文件操作
Go中文件操作的基础类型是[]byte,字节切片;
文件打开
Go内置的os包,主要通过Open和OpenFile方法对文件进行操作;(Open方法实际上也是调用OpenFile,OpenFile能做到更加精细的控制,以不同的模式打开文件)
普通Open打开文件
1 2 3 4 5
| file, err := os.Open(filePath) if err != nil { return err }
|
Open默认只读模式打开文件
OpenFile打开文件
1 2 3 4
| file, err := os.OpenFile(filePath, os.O_WRONLY, 0666) if err != nil { return err }
|
OpenFile函数可以控制以什么方式打开文件,上述代码是只写模式,所以无法读取到文件里面的内容;
具体控制的细节:
1 2 3 4 5 6 7 8 9 10 11 12
| const ( // 只读,只写,读写 三种必须指定一个 O_RDONLY int = syscall.O_RDONLY // 以只读的模式打开文件 O_WRONLY int = syscall.O_WRONLY // 以只写的模式打开文件 O_RDWR int = syscall.O_RDWR // 以读写的模式打开文件 // 剩余的值用于控制行为 O_APPEND int = syscall.O_APPEND // 当写入文件时,将数据添加到文件末尾 O_CREATE int = syscall.O_CREAT // 如果文件不存在则创建文件 O_EXCL int = syscall.O_EXCL // 与O_CREATE一起使用, 文件必须不存在 O_SYNC int = syscall.O_SYNC // 以同步IO的方式打开文件 O_TRUNC int = syscall.O_TRUNC // 当打开的时候截断可写的文件(清空文件) )
|
文件读取
Go内置的os提供了ReadFile直接读取文件内容;也能直接调用io.ReadAll方法直接读取打开的文件;
同样也可以参考ReadFile的源码,动态扩容缓存,调用本身os.File类型提供的Read方法,将文件内容读取到切片;
直接调用os.ReadFile方法读取文件
1 2 3 4 5
| data, err := os.ReadFile(filePath) if err != nil { return err }
|
传入对应文件路径,返回字节切片
io.ReadAll读取打开的文件
1 2 3 4 5 6 7 8 9 10
| f, err := os.Open(filePath) if err != nil { return err }
defer f.Close() data, err := io.ReadAll(f) if err != nil { return errors.Wrap(err, "读取文件数据失败") }
|
打开文件后,通过os.File的Read方法读取文件内容
代码参考的是os.ReadFile源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func AdvancedReadFile() (data []byte, err error) { f, err := os.Open(filePath) if err != nil { return }
defer f.Close()
data = make([]byte, 0, 512) for { if len(data) >= cap(data) { d := append(data[:cap(data)], 0) data = d[:len(data)] } n, err := f.Read(data[len(data):cap(data)]) data = data[:len(data)+n] if err != nil { if err == io.EOF { err = nil } return data, err } }
}
|
打开文件,创建一个缓存字节切片,通过无限循环不断读取文件数据,检查切片是否需要扩容,扩容后继续读取后续文件数据,最后读到文件关闭后,返回数据;
文件写入
可以直接通过OpenFile打开文件,模式需要设置只写模式或者读写模式,否则无法成功写入文件;os还有在前面基础上提供便捷函数,os.WriteFile和io.WriteString方式。
普通打开文件,通过os.File的Write和WriteString方法写入
1 2 3 4 5 6 7 8 9 10 11 12 13
| file, err := os.OpenFile(newFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666) if err != nil { return err }
fmt.Println("文件打开成功: ", file.Name()) for i := 0; i < 5; i++ { offset, err := file.WriteString("Hello,world\n") if err != nil { return errors.Wrap(err, "文件写入失败") } fmt.Println(offset) }
|
os.WriteFile直接将将数据写入文件
1 2 3 4
| err := os.WriteFile(path, []byte("hello,world\nhello,world\n"), 0666) if err != nil { return err }
|
WriteFile函数只是封装了打开文件的步骤,直接传入字符串即可
io.WriteString写入数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| file, err := os.OpenFile(newFilePath, os.O_RDWR|os.O_APPEND|os.O_TRUNC, 0666) if err != nil { return err }
fmt.Println("文件打开成功: ", file.Name()) for i := 0; i < 6; i++ { n, err := io.WriteString(file, "Hello,world\n") if err != nil { return errors.Wrap(err, "文件写入失败") } fmt.Println(n) } return nil
|
io.WriteString 不仅可以写文件,只要实现了io.Writer接口的,都可以写入(net.Conn、os.Stdout、bytes.Buffer),更加灵活;
复制
复制文件,本质上就是从某个文件读取内容,然后复制到目的文件,可以追加内容也可以覆盖内容,取决于打开文件的模式。以及os和io提供了封装的函数用于复制,本质上是实现了ReadFrom接口。
正常读取文件,然后写入到目的文件,完成复制操作
1 2 3 4 5 6 7 8 9 10 11 12 13
| func SimpleCopyFile() error { data, err := os.ReadFile(filePath) if err != nil { return err }
err = os.WriteFile(newFilePath, []byte(data), 0666) if err != nil { return err }
return nil }
|
注意这里使用的WriteFile方法,写入的文件是清空的,因为这个便捷函数直接默认Open打开的模式是O_TRUNC
ReadFrom直接从源文件中读取内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func AdvancedCopyFile(originPath, targetPath string) error { origin, err := os.OpenFile(originPath, os.O_RDONLY, 0666) if err != nil { return errors.Wrap(err, "打开源文件失败") } defer origin.Close()
target, err := os.OpenFile(targetPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { return errors.Wrap(err, "打开目的文件失败") } defer target.Close()
n, err := target.ReadFrom(origin) if err != nil { return errors.Wrap(err, "复制文件失败") } fmt.Println("文件复制成功", n) return nil }
|
这个示例代码中需要注意的是我是追加,所以复制的东西会一直追加到目标文件
io.Copy复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func IOCopyFile(originPath, targetPath string) error { origin, err := os.OpenFile(originPath, os.O_RDONLY, 0666) if err != nil { return errors.Wrap(err, "打开源文件失败") } defer origin.Close()
target, err := os.OpenFile(targetPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { return errors.Wrap(err, "打开目的文件失败") } defer target.Close()
n, err := io.Copy(target, origin) if err != nil { return errors.Wrap(err, "复制文件失败") } fmt.Println("文件复制成功", n) return nil }
|
可以对比和上面ReadFrom的代码,基本上相似的逻辑,只是在复制部分有点区别;阅读io的源码可知,io这部分判断是否实现了WriteTo或者ReadFrom接口,如果有直接调用对象的方法,如果没有则通过第一部分读取类似的逻辑,for循环读取文件的所有内容,然后写入。这种写法可以更好的兼容不同的类型,而不是拘泥于os.File;
删除
删除很简单,直接用os的函数Remove和RemoveAll分别删除文件和目录;
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func DeleteFile(path string) error { if err := os.Remove(path); err != nil { return err }
return nil }
func DeleteDir(path string) error { if err := os.RemoveAll(path); err != nil { return err } return nil }
|
备注
参考链接:
https://golang.halfiisland.com/essential/senior/100.io.html
https://pkg.go.dev/os@go1.22.4