本文首发于公众号:Hunter后端
原文链接:Golang基础笔记十四之文件操作
这一篇笔记介绍 Golang 里文件的相关操作,主要用的库是 io
。
以下是本篇笔记目录:
- 文件读取
- 文件写入
- 文件追加
- os.OpenFile()
- 文件属性
1、文件读取
1. 文件的打开与关闭
1) os.Open()
打开一个文件可以使用 os.Open()
函数,其代码示例如下:
filePath := "a.txt" file, err := os.Open(filePath)
我们可以通过判断 err 是否等于 nil 来决定是否可以接着读取文件,假设这里的 filePath 不存在,那么 err 则的信息则会是:
open file fail, err: open a.txt: The system cannot find the file specified.
file
的具体读取操作在后面再介绍。
2) file.Close()
在打开文件后,我们可以使用 defer file.Close()
操作来确保文件最后会被正常关闭。
2. 文件内容的读取
在文件打开以后,介绍几种对文件进行读取的方式,以下是打开文件的代码:
filePath := "a.txt" file, err := os.Open(filePath) if err != nil { fmt.Println("open file fail, err:", err) return } defer file.Close()
假设 a.txt
文件内容为:
第一行 second_line end of line
1) 一次性读取
如果目标文件不大,希望一次性读取文件内容的话,可以使用 io.ReadAll()
函数:
data, err := io.ReadAll(file) if err != nil { fmt.Println("read file error: ", err) return } fmt.Println("read file data: ", string(data)) return
返回的结果是一个 []byte 类型,可以使用 string()
将其转换为字符串。
2) 分块读取
如果文件过大,我们可以分块读取,每次读取指定字节数的数据,下面提供一个示例,用于分批次读取文件内容,直到读完整个文件:
data := make([]byte, 6) for { count, err := file.Read(data) if err == io.EOF { fmt.Println("end of file, exit") break } if err != nil { fmt.Println("Error: ", err) break } if count > 0 { fmt.Println("read count: ", count, ", data: ", string(data[:count])) } }
这里我们定义了一个长度为 6 的 byte 数组,然后在 for 循环里一直使用 file.Read()
读取,每次都往 data
中填充数据,直到读取到文件末尾,或者读取出现 error。
在这里需要注意,Go 里对读取文件到末尾的信息包装成了一个 error,我们需要进行判断下。
file.Seek()
在读取文件内容的时候,我们还可以指定指针读取的位置,比如重置读取的指针到开头,我们可以如下操作:
file.Seek(0, io.SeekStart)
file.Seek()
函数接收两个参数,一个是偏移量,一个是起始位置,上面这行代码的含义就是从文件开头的偏移量为 0 的位置开始读取文件内容。
如果我们想从文件开头往后三个字节长度的地方开始读取,可以如下操作:
file.Seek(3, io.SeekStart)
而指定读取有三个参数:
const ( SeekStart = 0 // seek relative to the origin of the file SeekCurrent = 1 // seek relative to the current offset SeekEnd = 2 // seek relative to the end )
分别表示文件开头,指针当前位置和文件末尾。
当然,file.Seek()
的第一个参数也可以是负数,比如我们想读取文件最后六个字节的内容,可以如下操作:
file.Seek(-6, io.SeekEnd)
3) 按行读取
我们可以使用 bufio.NewScanner()
函数来按行读取文件内容:
file.Seek(0, io.SeekStart) scanner := bufio.NewScanner(file) for scanner.Scan() { fmt.Println(scanner.Bytes(), scanner.Text()) }
这里,我们使用了两个内容,一个是 .Bytes()
,一个是 .Text()
,分别打印的内容是该行的字节数组和字符串数据。
2、文件写入
1. 文件的打开与关闭
文件写入的操作中,打开与关闭一个文件的操作如下:
filePath := "a.txt" file, err := os.Create(filePath) if err != nil { fmt.Println("create file error: ", err) return } defer file.Close()
使用到的函数是 os.Create()
,在这里,如果目标文件 filePath
不存在则会自动创建一个文件,如果存在,则会清空原来的数据,重新写入。
2. 文件内容的写入
文件打开以后,下面介绍几种写入内容的方式
1) file.Write()
可以直接使用 file
以字节数组的形式往文件写入数据:
n, err := file.Write([]byte("first line\n")) if err != nil { fmt.Println("write error: ", err) return } fmt.Printf("write %d bytes", n)
这里需要注意,如果要换行需要在末尾手动加上 \n
字符。
2) io.WriteString()
我们也可以使用 io.WriteString()
函数往文件里写入数据:
n, err := io.WriteString(file, "first line\n") if err != nil { fmt.Println("write error: ", err) return } fmt.Printf("write %d ", n)
3) bufio.NewWriter()
我们还可以使用 bufio.NewWriter()
函数写入,这种操作是以缓冲的形式写入,操作示例如下:
writer := bufio.NewWriter(file) n, err := writer.WriteString("first line\n") if err != nil { fmt.Println("write error: ", err) return } writer.Flush() fmt.Printf("write %d bytes\n", n)
这种操作需要在最后使用 writer.Flush()
函数将数据从缓冲区写入文件。
3、文件追加
1. 文件的打开与关闭
如果要对文件内容进行追加,我们可以使用 os.OpenFile()
函数,以下是一个使用示例:
filePath := "a.txt" file, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { fmt.Println("append file error: ", err) return } defer file.Close()
在这里,os.OpenFile()
函数接受三个参数,第一个是文件地址,第二个是标志位,第三个是文件的权限。
对于标志位,这里的 os.O_APPEND、os.O_WRONLY、os.O_CREATE 分别表示追加,只写和创建,这样即便是文件不存在也不会报错,而是会创建一个新文件。
2. 文件内容的追加
追加操作可以使用 file.Write()
来写入字节数组,或者 file.WriteString()
写入字符串:
file.Write([]byte("hello write byte\n")) file.WriteString("hello write string\n")
4、os.OpenFile()
这里再单独介绍一下 os.OpenFile()
函数,这个函数在前面追问文件内容的时候已经使用过一次了,这里着重再讲一下。
os.OpenFile()
函数是上面介绍的这些操作的基础函数,也就是说读取文件使用的 os.Open()
,写入文件使用的 os.Create()
,在底层的逻辑里都是调用的 os.OpenFile()
,不过是在具体实现的时候,根据不同的目标,比如读取或者写入来传入不同的参数以实现具体功能。
先来介绍 os.OpenFile()
函数的参数。
1. os.OpenFile() 参数
这个函数接收三个参数,name,flag 和 perm。
1) name
name 就是文件名称,string 类型,表示我们需要操作的目标文件。
2) flag
flag 表示的是操作的目的,比如前面介绍追加文件的时候用到的 os.O_APPEND|os.O_WRONLY|os.O_CREATE
。
参数类型是 int,在源码里定义了一系列关于文件的操作,如下:
const ( // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified. O_RDONLY int = syscall.O_RDONLY // open the file read-only. O_WRONLY int = syscall.O_WRONLY // open the file write-only. O_RDWR int = syscall.O_RDWR // open the file read-write. // The remaining values may be or'ed in to control behavior. O_APPEND int = syscall.O_APPEND // append data to the file when writing. O_CREATE int = syscall.O_CREAT // create a new file if none exists. O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist. O_SYNC int = syscall.O_SYNC // open for synchronous I/O. O_TRUNC int = syscall.O_TRUNC // truncate regular writable file when opened. )
比如这里有 O_RDONLY 表示只读,O_WRONLY 表示只写,O_RDWR 表示读和写,在操作文件的时候,这几个参数之一是必传的,用来表示文件操作的目的是读或者写。
下面几个参数则需要和其他上面的几个参数之一合并使用,比如 O_APPEND 追加,O_CREATE 创建等。
上面我们介绍追加功能的时候,就是一个示例,内容是 os.O_APPEND|os.O_WRONLY|os.O_CREATE
,这个操作首先通过必传的 os.O_WRONLY
表示是一个写操作,然后通过 os.O_APPEND
表示是追加操作,会在文件的末尾接着写入,而 os.O_CREATE
则表示如果目标文件不存在则创建一个新文件。
通过这种操作叠加的方式使我们追加文件的程序变得更健壮,不会因为文件不存在而报错。
3) perm
perm 表示权限,指的是我们操作文件的时候,对文件赋予的权限,和 Linux 上文件操作的权限是一致的,比如 0644
代表的含义是当前用户拥有可读可写,同用户组和其他用户组只拥有可读权限。
2. os.Open() 和 os.Create()
前面介绍了 os.Open() 和 os.Create() 分别用来读取和写入文件的操作示例,这两个函数背后也是通过调用 os.OpenFile() 来实现的。
1) os.Open()
os.Open() 函数在源代码中的定义如下:
func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0) }
可以看到通过 OpenFile() 给了一个只读的权限实现了读取文件内容的操作。
2) os.Create()
os.Create() 函数的源码如下:
func Create(name string) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) }
os.Create() 函数则是通过读写操作,不存在就创建文件操作,存在就对原文件进行截断操作的方式来实现写入。
3. os.ReadFile() 和 os.WriteFile()
除了上面介绍的读写操作,这里还介绍两个以 os.OpenFile()
函数为基础实现的读写操作,不过这两个函数的读和写都是一次性的,也就是会一次性读取文件全部内容,或者一次性写入全部内容。
1) os.ReadFile()
os.ReadFile() 函数操作示例如下:
filePath := "a.txt" data, err := os.ReadFile(filePath) fmt.Println("data: ", string(data), ", err: ", err)
返回的是一个字节数组,如果想要按行进行切割,可以使用 strings.Split(string(data), "\n")
操作。
2) os.WriteFile()
os.WriteFile() 操作示例如下:
err = os.WriteFile(filePath, []byte("hello write byte\nok write done\nlast line write\n"), 0644) fmt.Println("write error: ", err)
这里将多行数据使用 \n
进行分隔。
5、文件属性
打开一个文件后,我们可以获取这个文件的相关属性。
可以如下操作:
filePath := "a.txt" file, _ := os.Open(filePath) info, err := file.Stat() if err != nil { fmt.Println("error: ", err) } defer file.Close()
我们通过 file.Stat() 获取 FileInfo,文件的信息就都在 info 里了:
fmt.Println("文件名称: ", info.Name()) fmt.Printf("文件大小为 %d bytes\n", info.Size()) fmt.Printf("文件权限:%s, %o \n", info.Mode(), info.Mode()) fmt.Println("文件上次修改时间为:", info.ModTime())
这里文件权限打印出的字符串是 -rw-rw-rw-
,然后我们打印其八进制的内容就是常见的 666
形式了。
这一切,似未曾拥有