go 文件与目录操作
文件打开与关闭
文件打开
原始的文件打开函数:
func OpenFile(name string, flag int, perm FileMode) (*File, error)
name:绝对路径或相对路径(相对于进程当前工作目录)
flag:指定文件的访问模式,在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 // 打开文件用于同步 I/O O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件 )
其中,O_RDONLY、O_WRONLY、O_RDWR 应该只指定一个,剩下的通过 | 操作符来指定。该函数内部会给 flags 加上 syscall.O_CLOEXEC,在 fork 子进程时会关闭通过 OpenFile 打开的文件,即子进程不会重用该文件描述符。
perm:指定了文件的模式和权限位,类型是 os.FileMode,文件模式位常量定义在 os
const ( // 单字符是被 String 方法用于格式化的属性缩写。 ModeDir FileMode = 1 << (32 - 1 - iota) // d: 目录 ModeAppend // a: 只能写入,且只能写入到末尾 ModeExclusive // l: 用于执行 ModeTemporary // T: 临时文件(非备份文件) ModeSymlink // L: 符号链接(不是快捷方式文件) ModeDevice // D: 设备 ModeNamedPipe // p: 命名管道(FIFO) ModeSocket // S: Unix 域 socket ModeSetuid // u: 表示文件具有其创建者用户 id 权限 ModeSetgid // g: 表示文件具有其创建者组 id 的权限 ModeCharDevice // c: 字符设备,需已设置 ModeDevice ModeSticky // t: 只有 root/ 创建者能删除 / 移动文件 // 覆盖所有类型位(用于通过 & 获取类型位),对普通文件,所有这些位都不应被设置 ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice ModePerm FileMode = 0777 // 覆盖所有 Unix 权限位(用于通过 & 获取类型位) )
以上常量在所有操作系统都有相同的含义(可用时),因此文件的信息可以在不同的操作系统之间安全的移植。不是所有的位都能用于所有的系统,唯一共有的是用于表示目录的 ModeDir 位。
以上这些被定义的位是 FileMode 最重要的位。另外 9 个位(权限位)为标准 Unix rwxrwxrwx 权限(所有人都可读、写、运行)。
FileMode 还定义了几个方法,用于判断文件类型的 IsDir() 和 IsRegular(),用于获取权限的 Perm()。
返回的 error,具体实现是 *os.PathError,它会记录具体操作、文件路径和错误原因。
一般打开文件使用下面这两个函数:
func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0) } func Create(name string) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) }
文件关闭
close()
系统调用关闭一个打开的文件描述符,并将其释放回调用进程,供该进程继续使用。当进程终止时,将自动关闭其已打开的所有文件描述符。
func (f *File) Close() error
os.File.Close()
是对 close()
的封装。我们应该养成关闭不需要的文件的良好编程习惯。文件描述符是资源,Go 的 gc 是针对内存的,并不会自动回收资源,如果不关闭文件描述符,长期运行的服务可能会把文件描述符耗尽。
所以,通常的写法如下:
file, err := os.Open("/tmp/studygolang.txt") if err != nil { // 错误处理,一般会阻止程序往下执行 return } defer file.Close()
关于返回值 error
以下两种情况会导致 Close
返回错误:
1. 关闭一个未打开的文件;
2. 两次关闭同一个文件;
通常,我们不会去检查 Close
的错误。
文件打开与关闭示例
package main import ( "bufio" "fmt" "io" "io/ioutil" "os" ) func main00(){ //打开文件与1关闭文件 file,err :=os.Open("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt") if err == nil { fmt.Printf("文件打开成功") fmt.Println(file) } else{ fmt.Printf("文件打开失败,err:",err) return } defer func(){ file.Close() fmt.Printf("关闭文件") }() // }
判断文件是否存在
//使用os包状态检测结合os.IsNotExist(err)判断文件是否存在 //获取指定文件的信息 fileInfo,err := os.Stat("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt") if err != nil { fmt.Println("err=",err) //校验错误是否为【文件不存在错误】 if os.IsNotExist(err){ fmt.Println("文件不存在!") } return }else{ fmt.Println("文件存在!") fmt.Println(fileInfo) }
文件读取
func (f *File) Read(b []byte) (n int, err error)
Read 方法从 f 中读取最多 len(b) 字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取 0 个字节且返回值 err 为 io.EOF。
从方法声明可以知道,File 实现了 io.Reader 接口。
Read 对应的系统调用是 read。
对比下 ReadAt 方法:
func (f *File) ReadAt(b []byte, off int64) (n int, err error)
ReadAt 从指定的位置(相对于文件开始位置)读取长度为 len(b) 个字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。当 n<len(b) 时,本方法总是会返回错误;如果是因为到达文件结尾,返回值 err 会是 io.EOF。它对应的系统调用是 pread。
Read 和 ReadAt 的区别:前者从文件当前偏移量处读,且会改变文件当前的偏移量;而后者从 off 指定的位置开始读,且不会改变文件当前偏移量。
func main01(){ //以只读方式打开一个文件,创建其带缓冲的读取器,逐行读取到末尾 //4=readable,2=writeable,1=executeable,6=4+2 file,err :=os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_RDONLY,0666) //判断读入是否成功 if err == nil { fmt.Printf("文件打开成功") //打印内容 fmt.Println(file) } else{ fmt.Printf("文件打开失败,err:",err) return } //延迟关闭文件:在函数return前执行的程序 defer func(){ file.Close() fmt.Printf("关闭文件") }() //创建文件的读取器 Reader := bufio.NewReader(file) for{ //一次读取一行 data,err := Reader.ReadString('\n') if err == nil{ fmt.Println(data) }else{ //到达文件结尾,跳出循环 if err == io.EOF{ fmt.Println("已经文件结尾") break }else{ //读取异常,打印异常并结束 fmt.Println("读取失败,err:",err) return } } } }
文件写入
func (f *File) Write(b []byte) (n int, err error)
Write 向文件中写入 len(b) 字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值 n!=len(b),本方法会返回一个非 nil 的错误。
从方法声明可以知道,File 实现了 io.Writer 接口。
Write 对应的系统调用是 write。
Write 与 WriteAt 的区别同 Read 与 ReadAt 的区别一样。为了方便,还提供了 WriteString 方法,它实际是对 Write 的封装。
注意:Write 调用成功并不能保证数据已经写入磁盘,因为内核会缓存磁盘的 I/O 操作。如果希望立刻将数据写入磁盘(一般场景不建议这么做,因为会影响性能),有两种办法:
1. 打开文件时指定 `os.O_SYNC`;
2. 调用 `File.Sync()` 方法。
说明:File.Sync() 底层调用的是 fsync 系统调用,这会将数据和元数据都刷到磁盘;如果只想刷数据到磁盘(比如,文件大小没变,只是变了文件数据),需要自己封装,调用 fdatasync 系统调用。(syscall.Fdatasync)
func mian04(){ //以【创写追加】或【创写覆盖】方式打开一个文件,缓冲写入几行数据,倒干缓冲区 //打开文件,模式:不存在就创建+只写+追加,生成的文件的权限666 file,err :=os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_CREATE|os.O_WRONLY|os.O_APPEND,0666) //判断读入是否成功 if err != nil { fmt.Printf("文件打开失败,err:",err) return } //延迟关闭文件:在函数return前执行的程序 defer func(){ file.Close() fmt.Printf("关闭文件") }() //分批写入数据 writer := bufio.NewWriter(file) writer.WriteString("hello") writer.WriteString("2019") writer.WriteString("。。。。") //写入一个字符 writer.WriteRune('你') writer.WriteRune('好') //写一位数据 writer.WriteByte(123) //写一个字节数据,范围:0-255 writer.Write([]byte{123,222,21}) //把缓冲区清空,立即将最后的数据写入文件 writer.Flush() } func main05(){ //反引号代表保留原始排版的字符串 data := `测试文档、1、2、3、4、5。` fmt.Printf("data type=%T,value=%v\n",data,data) //将数据字符串转换为原始字节切片 dataBytes := []byte(data) fmt.Printf("data type=%T,value=%v\n",dataBytes,dataBytes) //向指定文件写入上面的字节数据 err := ioutil.WriteFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",dataBytes,0666) if err != nil { fmt.Println("写入错误") }else{ fmt.Println("写入成功") } }
文件拷贝
//使用ioutil包做一个傻瓜式拷贝 //使用io.Copy进行文件拷贝 //使用缓冲1K的缓冲区配合缓冲读写器进行图片拷贝 func main07(){ //使用ioutil包做一个傻瓜式拷贝 data,_:= ioutil.ReadFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt") err := ioutil.WriteFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test1.txt",data,0666) if err != nil{ fmt.Println("拷贝失败",err) }else{ fmt.Println("拷贝成功") } fmt.Println("执行完成") } func main08(){ //使用io.Copy进行文件拷贝 //打开拷贝源文件,模式为只读模式 srcFile,err := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_RDONLY,0666) //打开目标文件,模式为创建|写入 dstFile,err := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test1.txt",os.O_WRONLY |os.O_CREATE,0666) //执行源文件到目标文件的拷贝,writen为拷贝字节数 writen,err := io.Copy(dstFile,srcFile) if err == nil { fmt.Println("拷贝成功,字节数=",writen) }else{ fmt.Println("拷贝失败,err=",err) } } func main09(){ //使用缓冲1K的缓冲区配合缓冲读写器进行图片拷贝 //打开源文件 srcFile,_ := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test.txt",os.O_RDONLY,0666) //打开目标文件 dstFile,_ := os.OpenFile("C:/Users/Administrator/go/src/awesomeProject/demo/test/test1.txt",os.O_WRONLY |os.O_CREATE,0666) defer func(){ srcFile.Close() dstFile.Close() fmt.Println("文件全部关闭!") }() //创建源文件的缓冲读取器 reder := bufio.NewReader(srcFile) //创建目标文件的缓冲写出器 writer := bufio.NewWriter(dstFile) //创建缓冲区 buffer := make([]byte,1024) //将数据存入,直到io.EOF for { //循环的读取数据到缓冲区,然后把缓冲区的数据写入到目标文件 _,err :=reder.Read(buffer) if err !=nil{ if err == io.EOF{ fmt.Println("源文件读取完毕!") break }else{ fmt.Println("读取文件发生错误,err=",err) } }else{ _,err := writer.Write(buffer) if err !=nil { fmt.Println("写出错误,err=",err) return } } } }
改变文件偏移量:Seek
对于每个打开的文件,系统内核会记录其文件偏移量,有时也将文件偏移量称为读写偏移量或指针。文件偏移量是指执行下一个 Read
或 Write
操作的文件其实位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为 0。
文件打开时,会将文件偏移量设置为指向文件开始,以后每次 Read
或 Write
调用将自动对其进行调整,以指向已读或已写数据后的下一个字节。因此,连续的 Read
和 Write
调用将按顺序递进,对文件进行操作。
而 Seek
可以调整文件偏移量。方法定义如下:
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek
设置下一次读 / 写的位置。offset 为相对偏移量,而 whence 决定相对位置:0 为相对文件开头,1 为相对当前位置,2 为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。使用中,whence 应该使用 os
包中的常量:SEEK_SET
、SEEK_CUR
和 SEEK_END
。
注意:Seek
只是调整内核中与文件描述符相关的文件偏移量记录,并没有引起对任何物理设备的访问。
一些 Seek
的使用例子(file 为打开的文件对象),注释说明了将文件偏移量移动到的具体位置:
file.Seek(0, os.SEEK_SET) // 文件开始处 file.Seek(0, SEEK_END) // 文件结尾处的下一个字节 file.Seek(-1, SEEK_END) // 文件最后一个字节 file.Seek(-10, SEEK_CUR) // 当前位置前 10 个字节 file.Seek(1000, SEEK_END) // 文件结尾处的下 1001 个字节
最后一个例子在文件中会产生“空洞”。
Seek
对应系统调用 lseek
。该系统调用并不适用于所有类型,不允许将 lseek
应用于管道、FIFO、socket 或 终端。
文件目录操作
创建名称为name的目录,权限设置是perm,例如0777
func Mkdir(name string, perm FileMode) error
根据path创建多级子目录,例如astaxie/test1/test2。
func MkdirAll(path string, perm FileMode) error
删除名称为name的目录,当目录下有文件或者其他目录时会出错
func Remove(name string) error
根据path删除多级子目录,如果path是单个名称,那么该目录下的子目录全部删除。
func RemoveAll(path string) error
更改文件名,Rename
修改一个文件的名字或移动一个文件。如果 newpath
已经存在,则替换它。
func Rename(oldpath, newpath string) error
例子:
package main import ( "fmt" "os" ) func main() { os.Mkdir("test", 0777) os.MkdirAll("test/test1/test2", 0777) err := os.Remove("test") if err != nil { fmt.Println(err) } os.RemoveAll("test") }
笔记来源:
https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter06/06.1.html