文件的打开
文件的常见的两种打开方式是基于os
包所提供的两个函数:
func Open(name string) (*File,error) func OpenFile(name string flag int perm FileMode) (*File,error)
相对于前者,OpenFile
可以提供更加细致的操作,而前者就是对后者的一个简单封装
我们首先来看第一种使用方法,我们只需要提供对应的文件名就可以了,代码如下:
func main() { file, err := os.Open("test.txt") defer file.Close() if err != nil { fmt.Println("文件访问异常") return } }
文件的查找路径默认为项目go.mod
文件所在的路径,由于项目下并没有该文件,所以自然会返回一个错误。
因为IO错误的类型有很多,所以有时后需要我们去手动的去判断文件是否存在,而os
包也为此提供了方便函数,修改后的函数如下:
func main() { file, err := os.Open("test.txt") defer file.Close() if os.IsNotExist(err) { fmt.Println("file not found") } else if err != nil { fmt.Println("error") } else { fmt.Println("file found") } }
事实上第一种函数读取的文件仅仅只是只读的,无法被修改,Open
函数内部实现
func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0) }
通过OpenFile
函数可以控制更多细节,例如修改文件描述符和文件权限,关于文件描述符,os
包下提供了以下常量以供使用。
解释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 // 当打开的时候截断可写的文件 )
关于文件权限的则提供了以下常量。
解释const ( ModeDir = fs.ModeDir // d: 目录 ModeAppend = fs.ModeAppend // a: 只能添加 ModeExclusive = fs.ModeExclusive // l: 专用 ModeTemporary = fs.ModeTemporary // T: 临时文件 ModeSymlink = fs.ModeSymlink // L: 符号链接 ModeDevice = fs.ModeDevice // D: 设备文件 ModeNamedPipe = fs.ModeNamedPipe // p: 具名管道 (FIFO) ModeSocket = fs.ModeSocket // S: Unix 域套接字 ModeSetuid = fs.ModeSetuid // u: setuid ModeSetgid = fs.ModeSetgid // g: setgid ModeCharDevice = fs.ModeCharDevice // c: Unix 字符设备, 前提是设置了 ModeDevice ModeSticky = fs.ModeSticky // t: 黏滞位 ModeIrregular = fs.ModeIrregular // ?: 非常规文件 // 类型位的掩码. 对于常规文件而言,什么都不会设置. ModeType = fs.ModeType ModePerm = fs.ModePerm // Unix 权限位, 0o777 )
我们下面可以实现一个以读写模式打开一个文件的代码例子,权限为0666
,表示为所有人都可以对该文件进行读写,且不存在时会自动创建。
func main() { file, err := os.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0666) if os.IsNotExist(err) { fmt.Println("文件不存在") } else if err != nil { fmt.Println("文件打开有异常") } else { fmt.Println("文件打开成功", file.Name()) defer file.Close() } }
文件的读取
常见的文件读取
当我们成功打开文件以后,我们就可以开始进行读取操作了,对于读取文件的操作,os.file
提供了以下几个公开的方法
// 将文件读进传入的字节切片 func (f *File) Read(b []byte) (n int, err error) // 相较于第一种可以从指定偏移量读取 func (f *File) ReadAt(b []byte, off int64) (n int, err error) func
大部分情况下第一种情况使用的较多,针对第一种方法,我们需要自己编写逻辑来进行读取时切片的动态扩容,代码如下:
func ReadFile(f *os.File) ([]byte, error) { buffer := make([]byte, 0, 512) for { if len(buffer) == cap(buffer) { //扩容 buffer = append(buffer, 0)[:len(buffer)] } //继续读取 offerset, err := f.Read(buffer[len(buffer):cap(buffer)]) buffer = buffer[:len(buffer)+offerset] // 发生错误时 if err != nil { if errors.Is(err, io.EOF) { err = nil } return buffer, err } } }
剩余逻辑为:
func main() { file, err := os.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0666) if err != nil { fmt.Println("文件打开异常") } else { fmt.Println("文件打开成功", file.Name()) } bytes, err := ReadFile(file) if err != nil { fmt.Println("文件读取异常") } else { fmt.Println("文件读取成功", bytes) } file.Close() }
除此之外,我们还可以使用两个方便函数来进行文件读取,分别是os
包下的ReadFile
函数以及io
包下的ReadAll
函数,相对于前者而言,我们只需要提供文件路径即可,而后者我们则需要提供一个io.Raeder
类型的实现。
os.ReadFile
函数形式
func ReadFile(name string)([]byte,error)
使用例子:
func main() { bytes, err := os.ReadFile("README.txt") if err != nil { fmt.Println(err) } else { fmt.Println(string(bytes)) } }
io.ReadAll
函数形式:
func ReadAll(r Reader) ([]byte,error)
示例:
func main() { file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666) if err != nil { fmt.Println("文件访问异常") } else { fmt.Println("文件打开成功", file.Name()) bytes, err := io.ReadAll(file) if err != nil { fmt.Println(err) } else { fmt.Println(string(bytes)) } file.Close() } }
文件的写入
os.File
结构体提供以下几种方法来供我们写入数据:
//写入字节切片 func (f *file) Write(b []byte) (int,error) //写入字符串 func (f *file) WriteString(s string) (int,error) 从指定位置开始写,当以os.O_APPEND模式打开时,会返回错误 func (f *File) WriteAt(b []byte, off int64) (n int, err error)
如果我们要对文件写入数据。我们需要以O_WRONLY
或O_RDWR
模式打开文件,否则无法写入,接下来我们来看一个示例:
func main() { file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666) if err != nil { fmt.Println("文件访问异常") } else { fmt.Println("文件打开成功", file.Name()) for i := 0; i < 5; i++ { offset, err := file.WriteString("hello world!\n") if err != nil { fmt.Println(offset, err) } } fmt.Println(file.Close()) } }
向文件写入字节切片也是类似的操作,就不再赘述。对于写入文件的操作标准库同样提供了方便函数,分别是os.WriteFile
与io.WriteString
os.WriteFile
func WriteFile(name string, data []byte, perm FileMode) error • 1
使用例子如下
func main() { err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666) if err != nil { fmt.Println(err) } }
此时文件内容如下
hello world!
io.WriteString
func WriteString(w Writer, s string) (n int, err error)
使用例子如下
func main() { file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666) if err != nil { fmt.Println("文件访问异常") } else { fmt.Println("文件打开成功", file.Name()) for i := 0; i < 5; i++ { offset, err := io.WriteString(file, "hello world!\n") if err != nil { fmt.Println(offset, err) } } fmt.Println(file.Close()) } }
文件的复制
对于复制文件而言,需要同时打开两个文件,第一种方法是将原文件中的数据读取出来,然后写入目标文件中,代码示例如下
解释func main() { // 从原文件中读取数据 data, err := os.ReadFile("README.txt") if err != nil { fmt.Println(err) return } // 写入目标文件 err = os.WriteFile("README(1).txt", data, 0666) if err != nil { fmt.Println(err) } else { fmt.Println("复制成功") } }
os.File.ReadFrom
另一种方法是使用os.File
提供的方法ReadFrom
,打开文件时,一个只读,一个只写。
func (f *File) ReadFrom(r io.Reader) (n int64, err error) • 1
使用示例如下
解释func main() { // 以只读的方式打开原文件 origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666) if err != nil { fmt.Println(err) return } defer origin.Close() // 以只写的方式打开副本文件 target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { fmt.Println(err) return } defer target.Close() // 从原文件中读取数据,然后写入副本文件 offset, err := target.ReadFrom(origin) if err != nil { fmt.Println(err) return } fmt.Println("文件复制成功", offset) }
io.Copy
还有一种方法就是使用io.Copy
方便函数
func Copy(dst Writer, src Reader) (written int64, err error)
使用示例如下
解释func main() { // 以只读的方式打开原文件 origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666) if err != nil { fmt.Println(err) return } defer origin.Close() // 以只写的方式打开副本文件 target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { fmt.Println(err) return } defer target.Close() // 复制 written, err := io.Copy(target, origin) if err != nil { fmt.Println(err) } else { fmt.Println(written) } }
文件重命名
重命名也可以理解为移动文件,会用到os
包下的Rename
函数。
func Rename(oldpath, newpath string) error • 1
示例如下
解释func main() { err := os.Rename("README.txt", "readme.txt") if err != nil { fmt.Println(err) } else { fmt.Println("重命名成功") } }
**注意:**重命名文件夹,移动文件夹同样适用。
文件的删除
删除操作相较于其他操作要简单的多,只会用到os
包下的两个函数
解释// 删除单个文件或者空目录,当目录不为空时会返回错误 func Remove(name string) error // 删除指定目录的所有文件和目录包括子目录与子文件 func RemoveAll(path string) error
使用起来十分的简单,下面是删除目录的例子
解释func main() { // 删除当前目录下所有的文件与子目录 err := os.RemoveAll(".") if err != nil { fmt.Println(err) }else { fmt.Println("删除成功") } }
下面删除单个文件的例子