在 Golang 中内置包os中,打开文件使用 Open 函数(只读)和OpenFile函数(可切换模式),关闭文件使用 Close 函数,打开文件、关闭文件以及大多数文件操作都涉及到一个很重要的结构体 os.File 结构体。
实现了io.Reader
、io.Writer
、io.Closer
等接口
type File struct {
*file // file 指针
}
type file struct {
//是一个FD结构体类型,是一个文件的唯一标志,每一个被打开的文件在操作系统中,都会有一个文件标志符来唯一标识一个文件
pfd poll.FD
//文件名
name string
//文件的路径信息,也是一个结构体
dirinfo *dirInfo
//是一个 bool 类型,表明该文件是否可以被追加写入内容。
appendMode bool
}
type FileInfo interface {
Name() string // 文件的名字
Size() int64 // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
Mode() FileMode // 文件的模式位 (例-rw-rw-r--)
ModTime() time.Time // 文件的修改时间
IsDir() bool // 等价于Mode().IsDir()
Sys() interface{} // 底层数据来源(可以返回nil)
}
os.fileStat实现了FileInfo接口的所有方法,使用os.Stat(path)
函数可以获取path对应文件的fileStat实例
func Stat(name string) (FileInfo, error)
package main
import (
"fmt"
"os"
)
func main() {
info, err := os.Stat("./test.txt")
if err != nil {
fmt.Println(err)
}
//判断文件是否存在
if os.IsNotExist(err) {
fmt.Println("file not exist")
}
fmt.Println("name:", info.Name())
fmt.Println("size:", info.Size())
fmt.Println("mode:", info.Mode())
fmt.Println("modtime:", info.ModTime())
fmt.Println("isDir:", info.IsDir())
/*
name: test.txt
size: 21
mode: -rw-rw-rw-
modtime: 2022-03-28 14:09:32.6151527 +0800 CST
isDir: false
*/
}
var EOF = errors.New("EOF")
是 error 类型。根据 reader 接口的说明,在 n > 0 且数据被读完了的情况下,返回的 error 有可能是 EOF 也有可能是 nil。
路径相关的函数有两个包,path
和 path/filepath
,
两个包内有一些相同的函数,如IsAbs()
、Join()
、Dir()
filepath
中的函数兼容各个操作系统,涉及到windows系统路径操作时,应该使用filepath
包
filepath.Rel(basepath, targpath string) (string, error) //获取tarpath相对于basepath的相对路径
filepath.Abs(path string) (string, error) //获取绝对路径,如果path不是绝对路径,会加入当前工作目录以使之成为绝对路径。
path.Join(elem ...string) string//路径拼接
path.IsAbs(path string) bool//判断文件是否是绝对路径
path.Dir(path string) string//获取目录
package main
import (
"fmt"
"path/filepath"
)
func main() {
p1, _ := filepath.Rel("E:\\go-project\\demo\\", "E:\\go-project\\1.txt")
fmt.Println(p1) // ..\1.txt
p2, _ := filepath.Abs("./test.txt")
fmt.Println(p2) //E:\go-project\demo\test.txt
p3 := filepath.Join("E:\\go-project\\demo", "test\\hello.txt")
fmt.Println(p3) //E:\go-project\demo\test\hello.txt
p4 := filepath.IsAbs("E:\\go-project\\demo\\")
fmt.Println(p4) //true
p5 := filepath.Dir("E:\\go-project\\demo\\test.txt")
fmt.Println(p5) //E:\go-project\demo
}
//创建
os.Mkdir(name string, perm FileMode) error //创建目录,只创建一层,相当于mkdir
os.MkdirAll(path string, perm FileMode) error //创建多层目录,相当于mkdir -p
os.Create(name string) (file *File, err error) //创建文件,如果存在则覆盖
//移动
func Rename(oldpath, newpath string) error //重命名、移动文件
//删除
func Remove(name string) error //删除单个
func RemoveAll(path string) error //递归删除目录
Open 函数接受一个字符串类型的文件名做为参数,如果打开成功,则返回一个 File 结构体的指针,否则就返回error错误信息。
底层使用的是OpenFile(name, O_RDONLY, 0)
,只读
os.Open(name string) (*File, error)
os.OpenFile(name string, flag int, perm FileMode) (file *File, err error)
//name:文件路径
//flag:文件打开方式,多个可使用|分隔开
//perm:打开文件的模式,即权限,例如0066、0777
flag可选参数
打开方式 | 说明 |
---|---|
O_RDONLY | 只读方式打开 |
O_WRONLY | 只写方式打开 |
O_RDWR | 读写方式打开 |
O_APPEND | 追加方式打开 |
O_CREATE | 不存在,则创建 |
O_EXCL | 如果文件存在,且标定了O_CREATE的话,则产生一个错误 |
O_TRUNG | 如果文件存在,且它成功地被打开为只写或读写方式,将其长度裁剪唯一。(覆盖) |
O_NOCTTY | 如果文件名代表一个终端设备,则不把该设备设为调用进程的控制设备 |
O_NONBLOCK | 如果文件名代表一个FIFO,或一个块设备,字符设备文件,则在以后的文件及I/O操作中置为非阻塞模式。 |
O_SYNC | 当进行一系列写操作时,每次都要等待上次的I/O操作完成再进行。 |
使用 File 指针来调用 Close 函数,如果关闭失败会返回 error 错误信息。
func (file *File) Close() error
Seek设置下一次读/写的位置。
offset为相对偏移量
whence决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。
它返回新的偏移量(相对开头)和可能的错误。
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Truncate改变文件的大小,它不会改变I/O的当前位置。 如果截断文件,多出的部分就会被丢弃,也就是从头开始保留size个字节。如果出错,错误底层类型是*PathError
,size = 0
则清空文件。
func (f *File) Truncate(size int64) error
也可以使用os包提供的方法,一样的作用,name
为文件名
func Truncate(name string, size int64) error
由于os.File实现了io.Reader、io.Writer接口,所以这里实际调用的是File中实现的Read和Write方法
//读,文件读取结束的标志是返回的 n 等于 0,并且err会返回io.EOF,单次读取的大小和b的大小一样
func (f *File) Read(b []byte) (n int, err error)
//写,以字节数组写入
func (f *File) Write(b []byte) (n int, err error)
//写,以字符串写入
func (f *File) WriteString(s string) (n int, err error)
//复制文件
io.Copy(dst io.Writer, src io.Reader) (written int64, err error)
func (f *File) Read(b []byte) (n int, err error)
,其中b
为保存读到数据的字节切片,n
为读到的字节长度,如果读到文件末尾,方法会返回0,io.EOF
package main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
fmt.Println(err)
}
defer file.Close()
//定义切片存放每次读取字节
data := make([]byte, 1024)
//声明切片,将每次读到的数据存放起来
var result []byte
for {
i, err := file.Read(data)
if err != nil && err != io.EOF {
fmt.Println(err)
break
}
if i == 0 {
break
}
result = append(result, data[:i]...)
}
fmt.Println(string(result))
}
func (f *File) Write(b []byte) (n int, err error)
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("./test.txt", os.O_RDWR|os.O_CREATE, 0777)
if err != nil {
fmt.Println(err)
}
defer file.Close()
s := "hello world\nhello golang"
//写入文件,将字符串转字节数组,也可以直接使用WriteString
n, err := file.Write([]byte(s))
if err != nil {
fmt.Println(err)
} else {
fmt.Println("write success,byte count =", n)
}
}
func Copy(dst Writer, src Reader) (written int64, err error)
,此处注意,由于File已经实现了Writer、Reader接口,所以可以直接传入File实例
package main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
fmt.Println(err)
}
defer file.Close()
fileCp, err := os.OpenFile("./testCp.txt", os.O_CREATE|os.O_WRONLY, 0777)
if err != nil {
fmt.Println(err)
}
defer fileCp.Close()
n, err := io.Copy(fileCp, file)
if err != nil {
fmt.Println(err)
}
fmt.Println("copy success,n =", n)
}
//读,由于这两个方法都是一次读取文件全部数据,所以不适合大文件
ioutil.ReadFile(filename string) ([]byte, error)
//读,这个方法需要打开一个文件,并将文件句柄传入参数
ioutil.ReadAll(r io.Reader) ([]byte, error)
//读取单层目录下所有文件(夹),返回FileInfo切片
ioutil.ReadDir(dirname string) ([]fs.FileInfo, error)
//写,文件不存在则创建文件,存在则清空文件,FileMode可以直接用0666或者0777
ioutil.WriteFile(filename string, data []byte, perm os.FileMode)
package main
import (
"fmt"
"io/ioutil"
)
func main() {
data, err := ioutil.ReadFile("./test.txt")
if err != nil {
fmt.Println(err)
}
//将字节数组转为string类型
fmt.Println(string(data))
}
package main
import (
"fmt"
"io/ioutil"
)
func main() {
s := "hello world\nhello golang"
err := ioutil.WriteFile("./test.txt", []byte(s), 0777)
if err != nil {
fmt.Println(err)
}
}
bufio 包实现了缓存IO。它包装了 io.Reader 和 io.Writer 对象,创建了另外的Reader和Writer对象,它们也实现了 io.Reader 和 io.Writer 接口,不过它们是有缓存的。
io操作本身的效率并不低,低的是频繁的访问本地磁盘的文件。所以bufio就提供了缓冲区(分配一块内存),读和写都先在缓冲区中,最后再读写文件,来降低访问本地磁盘的次数,从而提高效率。
bufio.Read(p []byte)
相当于每次读取大小len(p)的内容
len(p)>len(buf)
,即要读取的内容比缓存区还要大,直接去文件读取即可len(p)<len(buf)
,即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容,供下一次读取,这样也就减少了下次一读取磁盘io的数据量)bufio.Write(p []byte)
相当于写入len(p)大小的内容
bufio.Reader
//NewReader 函数用来返回一个默认大小 buffer 的 Reader 对象(默认大小是 4096)
func NewReader(rd io.Reader) *Reader
//NewReaderSize 返回一个指定大小 buffer(size 最小为 16)的 Reader 对象,如果 io.Reader 参数已经是一个足够大的 Reader,它将返回该 Reader
func NewReaderSize(rd io.Reader, size int) *Reader
//Peek 返回缓存的一个切片,该切片引用缓存中前 n 个字节的数据,该操作不会将数据读出,只是引用
func (b *Reader) Peek(n int) ([]byte, error)
//Read 从 b 中读出数据到 p 中,返回读出的字节数和遇到的错误
func (b *Reader) Read(p []byte) (n int, err error)
//Buffered 返回缓存中未读取的数据的长度
func (b *Reader) Buffered() int
//ReadSlice 从输入中读取,直到遇到第一个界定符(delim)为止,返回一个指向缓存(也就意味着随着缓存改变而改变,所以一般用ReadBytes和ReadString)中字节的 slice,在下次调用读操作(read)时,这些字节会无效,如果在找到界定符之前缓存已经满了,ReadSlice 会返回 bufio.ErrBufferFull 错误
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
//ReadBytes 和ReadSlice区别就是返回的是当前缓存中字节slice的一个拷贝
func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
//ReadString 和ReadBytes一样,只不过返回的是字符串,底层调用的是ReadBytes
func (b *Reader) ReadString(delim byte) (string, error)
//ReadLine 逐行读取,底层调用的是ReadSlice('\n')
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
//读取单个UTF-8编码的Unicode字符,并返回字符及其字节大小
func (b *Reader) ReadRune() (r rune, size int, err error)
bufio.Writer
//返回默认缓冲大小的 Writer 对象(默认是4096),关闭文件前一定要Flush,将缓冲区的数据写入w
func NewWriter(w io.Writer) *Writer
//指定缓冲大小创建一个 Writer 对象,关闭文件前一定要Flush,将缓冲区的数据写入w
func NewWriterSize(w io.Writer, size int) *Writer
//写入单个字节,直接写入文件
func (b *Writer) WriteByte(c byte) error
//写入单个 Unicode 指针返回写入字节数错误信息,直接写入文件
func (b *Writer) WriteRune(r rune) (size int, err error)
//写入字符串并返回写入字节数和错误信息,直接写入文件
func (b *Writer) WriteString(s string) (int, error)
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
fmt.Println(err)
}
defer file.Close()
fileBuf := bufio.NewReader(file)
data := make([]byte, 1024)
var result []byte
for {
i, err := fileBuf.Read(data)
if err != nil && err != io.EOF {
fmt.Println(err)
break
}
if i == 0 {
break
}
result = append(result, data[:i]...)
}
fmt.Println(string(result))
}
逐字读取utf8字符
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("./test.txt", os.O_RDWR, 777)
if err != nil {
fmt.Errorf("%+v", err)
}
reader := bufio.NewReader(file)
r, i, err := reader.ReadRune()
for i > 0 && err == nil {
fmt.Print(string(r))
r, i, err = reader.ReadRune()
}
file.Close()
}
注意:关闭文件前一定要Flush()
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("./test.txt", os.O_WRONLY|os.O_CREATE, 0777)
if err != nil {
fmt.Println(err)
}
defer file.Close()
fileBuf := bufio.NewWriter(file)
s := "hello world\nhello golang"
fileBuf.Write([]byte(s))
//关闭文件前一定要fulsh,将缓冲区内的数据写入文件
fileBuf.Flush()
}
os.Stdin
:标准输入的文件实例,类型为*File
os.Stdout
:标准输出的文件实例,类型为*File
os.Stderr
:标准错误输出的文件实例,类型为*File
package main
import "os"
func main() {
var buf [16]byte
os.Stdin.Read(buf[:])
os.Stdin.WriteString(string(buf[:]))
}
ioutil提供了两个函数: TempDir() 和 TempFile()。使用完毕后,调用者负责删除这些临时文件和文件夹。有一点好处就是当你传递一个空字符串作为文件夹名的时候,它会在操作系统的临时文件夹中创建这些项目(/tmp on Linux)。os.TempDir()返回当前操作系统的临时文件夹。
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
)
func main() {
// 在系统临时文件夹中创建一个临时文件夹
tempDirPath, err := ioutil.TempDir("", "myTempDir")
if err != nil {
log.Fatal(err)
}
fmt.Println("Temp dir created:", tempDirPath)
// 在临时文件夹中创建临时文件
tempFile, err := ioutil.TempFile(tempDirPath, "myTempFile.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("Temp file created:", tempFile.Name())
// ... 做一些操作 ...
// 关闭文件
err = tempFile.Close()
if err != nil {
log.Fatal(err)
}
// 删除我们创建的资源
err = os.Remove(tempFile.Name())
if err != nil {
log.Fatal(err)
}
err = os.Remove(tempDirPath)
if err != nil {
log.Fatal(err)
}
}