这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
Created: January 15, 2023 9:21 PM Tags: golang
文件读写操作
Go 和 C 一样提供了多种文件操作的方式,我们可以对这些方式做一个简单的梳理,以便于在不同场景下选择合适的文件操作方式。
文件的打开和关闭
首先和 C 语言一样,Go 语言中操作文件也是通过一个 FILE 结构体:
type file struct {
pfd poll.FD
name string
dirinfo *dirInfo
}
type File struct {
*file // os specific
}
func Open
func Open(name string) (*File, error)
Open 函数用于打开一个指定的文件,如果读取成功将返回对应的文件对象,如果读取失败将返回 *PathError 错误类型。
func OpenFile
func OpenFile(name string, flag int, perm FileMode) (*File, error)
OpenFile 函数可以使用指定的选项与指定的模式来打开文件,同样如何文件打开成功,返回一个可用于 IO 的文件对象,否则返回一个 *PathError 类型的错误。
OpenFile 函数接受一个字符串类型的文件名参数,同时还接受一个 int 类型的文件打开方式参数和一个打开模式参数,打开模式使用的是 Linux 中的文件模式。
打开方式参数 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操作完成再进行。 |
打开模式参数 perm:
Unix 使用 -rwxrwxrwx 这样的形式来表示文件权限,其中:
- 第1位:文件属性,`` 表示是普通文件,
d表示是一个目录 - 第2-4位:文件所有者的权限
- 第5-7位:文件所属用户组的权限
- 第8-10位:其他人的权限
| 打开模式 | 文件权限 | 说明 |
|---|---|---|
| 0777 | -rwxrwxrwx | 创建了一个普通文件,所有人拥有所有的读、写、执行权限 |
| 0666 | -rw-rw-rw- | 创建了一个普通文件,所有人拥有对该文件的读、写权限,但是都不可执行 |
| 0644 | -rw-r--r-- | 创建了一个普通文件,文件所有者对该文件有读写权限,用户组和其他人只有读权限,没有执行权限 |
示例
package main
import (
"fmt"
"os"
)
func main() {
// 1.打开一个文件
// 注意: 文件不存在不会创建, 会报错
// 注意: 通过Open打开只能读取, 不能写入
fp, err := os.Open("test.txt")
if err != nil{
fmt.Println(err)
}else{
fmt.Println(fp)
}
// 2.关闭一个文件
defer func() {
err = fp.Close()
if err != nil {
fmt.Println(err)
}
}()
}
文件读取
func Read(不带缓冲区去读)
func (f *File) Read(b []byte) (n int, err error)
Read 方法从 f 中读取最多 len(b) 字节数据并写入b
package main
import (
"fmt"
"io"
"os"
)
func main() {
// 1.打开一个文件
// 注意: 文件不存在不会创建, 会报错
// 注意: 通过Open打开只能读取, 不能写入
fp, err := os.Open("test.txt")
if err != nil{
fmt.Println(err)
}else{
fmt.Println(fp)
}
// 2.关闭一个文件
defer func() {
err = fp.Close()
if err != nil {
fmt.Println(err)
}
}()
// 3.读取指定指定字节个数据
// 注意点: \n也会被读取进来
//buf := make([]byte, 50)
//count, err := fp.Read(buf)
//if err != nil {
// fmt.Println(err)
//}else{
// fmt.Println(count)
// fmt.Println(string(buf))
//}
// 4.读取文件中所有内容, 直到文件末尾为止
buf := make([]byte, 10)
for{
count, err := fp.Read(buf)
// 注意: 这行代码要放到判断EOF之前, 否则会出现少读一行情况
fmt.Print(string(buf[:count]))
if err == io.EOF {
break
}
}
}
func ReadBytes 和 func ReadString(带缓冲区去读)
func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
• ReadBytes 读取直到第一次遇到 delim 字节
func (b *Reader) ReadString(delim byte) (line string, err error)
• ReadString 读取直到第一次遇到 delim 字节
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
// 1.打开一个文件
// 注意: 文件不存在不会创建, 会报错
// 注意: 通过Open打开只能读取, 不能写入
fp, err := os.Open("test.txt")
if err != nil{
fmt.Println(err)
}else{
fmt.Println(fp)
}
// 2.关闭一个文件
defer func() {
err = fp.Close()
if err != nil {
fmt.Println(err)
}
}()
// 3.读取一行数据
// 创建读取缓冲区, 默认大小4096
//r :=bufio.NewReader(fp)
//buf, err := r.ReadBytes('\n')
//buf, err := r.ReadString('\n')
//if err != nil{
// fmt.Println(err)
//}else{
// fmt.Println(string(buf))
//}
// 4.读取文件中所有内容, 直到文件末尾为止
r :=bufio.NewReader(fp)
for{
//buf, err := r.ReadBytes('\n')
buf, err := r.ReadString('\n')
fmt.Print(string(buf))
if err == io.EOF{
break
}
}
}
bufio.NewReader() 函数返回的是一个 Reader 对象,Reader 对象可以对数据 I/O 接口 io.Reader 进行输入缓冲操作,Reader 结构定义如下:
type Reader struct {
//contains filtered or unexported fields
)
默认情况下 Reader 对象没有定义初始值,输入缓冲区最小值为 16。当超出限制时,另创建一个二倍的存储空间。
func ReadFile
func ReadFile(filename string) ([]byte, error)
• 从 filename 指定的文件中读取数据并返回文件的所有内容。ioutil.ReadFile(filename string) 方法能够将文件所有字节读取出来,省去了使用字节缓存循环读取的过程。但要注意该方法不适合大文件读取。
package main
import (
"fmt"
"io/ioutil"
)
func main() {
filePath := "test.txt"
buf, err := ioutil.ReadFile(filePath)
if err !=nil {
fmt.Println(err)
}else{
fmt.Println(string(buf))
}
}
func Scanner
有时候为了便于分析处理,我们希望能够逐行读取文件内容,这个时候可以 Scanner 来完成:
// read-line-by-line.go
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
fp, err := os.Open("test.txt")
if err != nil{
fmt.Println(err)
}else{
fmt.Println(fp)
}
defer func() {
err = fp.Close()
if err != nil {
fmt.Println(err)
}
}()
// 接受io.Reader类型参数 返回一个bufio.Scanner实例
scanner := bufio.NewScanner(fp)
var count int
for scanner.Scan() {
count++
// 读取当前行内容
line := scanner.Text()
fmt.Printf("%d %s\n", count, line)
}
}
上面代码直接打印出了每一行的数据,如果大家想得到最终文件的内容,可以创建一个字符串切片,每次逐行扫描时,将当前行内容追加到切片中即可。
文件创建和写入
func Create
func Create(name string) (file *File, err error)
Create采用模式0666(任何人都可读写,不可执行)创建一个名为name的文件,如果文件存在会覆盖原有文件。
func Write
func (f *File) Write(b []byte) (n int, err error)
将指定字节数组写入到文件中
func WriteString
func (f *File) WriteString(s string) (ret int, err error)
将指定字符串写入的文件中
在打开文件之后,我们可以通过 Write() 和 WriteString() 方法写入数据,最后通过 Sync() 方法将数据持久化到磁盘:
package main
import (
"fmt"
"os"
)
func main() {
// 注意点: 第三个参数在Windows没有效果
// -rw-rw-rw- (666) 所有用户都有文件读、写权限。
// -rwxrwxrwx (777) 所有用户都有读、写、执行权限。
// fp, err := os.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0666)
fp, err := os.OpenFile("test.txt", os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err)
}
defer func() {
err := fp.Close()
if err != nil {
fmt.Println(err)
}
}()
// 注意点:
// 如果O_RDWR模式打开, 被打开文件已经有内容, 会从最前面开始覆盖
// 如果O_APPEND模式打开, 被打开文件已经有内容, 会从在最后追加
bytes := []byte{'h','e','l','l','o','\r','\n'}
fp.Write(bytes)
fp.WriteString("hello\r\n")
file.Sync()
}
除了上述这种不带缓冲区的写入方式,还有利用 bufio 的 Writer 对象实现带缓冲区的写入方式:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
fp, err := os.OpenFile("test.txt", os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err)
}
defer func() {
err := fp.Close()
if err != nil {
fmt.Println(err)
}
}()
// 创建缓冲区
w := bufio.NewWriter(fp)
// 4.写入数据到缓冲区
bytes := []byte{'h','e','l','l','o','\r','\n'}
w.Write(bytes)
w.WriteString("hello\r\n")
// 将缓冲区中的数据刷新到文件
w.Flush()
}