1. 文件句柄:os.File
Go
语言的标准库os
包下的File
结构体表示一个文件句柄,获得了os.File
,就可以对文件进行各种操作,有以下三种方式获得os.File
文件句柄:
1.1 os.Create
通过os.Create()
函数传入一个文件名称可以创建并获得一个表示该文件的os.File
结构体
1
| file,err := os.Create("./my.dat")
|
如果指定的文件不存在,调用该函数后,会创建文件,如果文件已经存在,则只会清空文件的内容。
1.2 os.Open
对于已经存在的文件,如果不想清空文件内容,只想打开该文件的话,可以用os.Open()
函数:
1
| file, err := os.Open("./my.dat")
|
用该函数打开一个文件句柄时,如果文件不存在,返回值err
返回一个error
类型的错误。
1.3 os.OpenFile
其实,os.Create()
函数和os.Open()
函数的底层都是调用os.OpenFile()
函数
1 2 3 4 5 6 7
| 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) }
|
os.OpenFile()
函数签名如下:
1
| func OpenFile(name string, flag int, perm FileMode) (*File, error)
|
从函数签名可以看到调用os.OpenFile
函数时要传入三个参数,其中name
是表示要打开的文件名。
而第二个参数flag
表示打开文件的标志,比较常用有以下几种取值:
- O_RDONLY:只读
- O_WRONLY:只写
- O_RDWR:读写
- O_APPEND:以追加的方式写入
- O_CREATE:文件不存在时创建
- O_TRUNC:当文件存在时,将文件内容置空
可以同时指定多个标志,多个标志用逻辑运算符|
连接起来,比如os.Create()
函数在调用os.OpenFile
函数时就传入多个标志:
1
| OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
|
无论是用哪种方式打开的文件句柄,最终都要记得关闭以释放资源,比较标准的用法是用defer
语句:
1 2 3 4 5 6
| name := "./my.dat" file,err := OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0755) if err != nil{ panic(err) } defer file.Close()
|
2. 读取文件
要读取文件的内容,在获得文件句柄时,OpenFile
函数的参数flag
只需要传O_RDONLY
就可以了,而参数FileMode
可以为0
:
1
| file, err := os.OpenFile("./my.dat", os.O_RDONLY, 0)
|
os.File
有一个Read()
方法,也就是说os.File
实现io.Reader
接口,Go标准库很多的包可以处理io.Reader
接口,比如ioutil
,bufio
,fmt
等,因此有很多种方式可以读取文件的内容。
2.1 直接读取
os.File
的Read
方法就可以直接将文件内容读取一个字节数组中,并返回读取的长度和一个用于判断是否出错的error
类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt" "io" "os" )
func main() { f, err := os.Open("./my.dat") if err != nil { panic(err) } defer f.Close() for { b := make([]byte, 10) n, err := f.Read(b) if n == 0 || err == io.EOF { return } fmt.Println(n, string(b)) } }
|
2.2 使用bufio 包读取
当要用bufio
包读取文件时,将调用bufio.NewReader()
函数将io.Reader
包装为一个bufio.Reader
结构体,该结构体封装了很多更便捷读取文件的方法:
1 2 3 4 5 6 7
| func (b *Reader) Read(p []byte) (n int, err error) func (b *Reader) ReadByte() (byte, error) func (b *Reader) ReadBytes(delim byte) ([]byte, error) func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) func (b *Reader) ReadRune() (r rune, size int, err error) func (b *Reader) ReadSlice(delim byte) (line []byte, err error) func (b *Reader) ReadString(delim byte) (string, error)
|
ReadLine
方法的使用示例:
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
| package main
import ( "bufio" "fmt" "io" "os" )
func main() { file, err := os.OpenFile("./my.dat", os.O_RDWR, 0666) if err != nil { panic(err) } defer file.Close() reader := bufio.NewReader(file) for { b, _, err := reader.ReadLine() if err == io.EOF { break } fmt.Println(string(b)) } }
|
2.3 使用fmt包读取文件
fmt
包以FScan...
开头的函数可以按一定的格式扫描读取文件里的内容:
1 2 3
| func Fscan(r io.Reader, a ...any) (n int, err error) func Fscanf(r io.Reader, format string, a ...any) (n int, err error) func Fscanln(r io.Reader, a ...any) (n int, err error)
|
Fscanln
方法的使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt" "io" "os" )
func main() { file, err := os.OpenFile("./my.dat", os.O_RDWR, 0666) if err != nil { panic(err) } defer file.Close() for { var a1, a2 string _, err := fmt.Fscanln(file, &a1, &a2) fmt.Println(a2, a2) if err == io.EOF { break } } }
|
2.4 使用ioutil包读取文件
标准库的ioutil
包对读取文件做好封装,可以直接读取整个文件的数据:
1 2 3 4 5 6
| f, err := os.Open("./my.dat") if err != nil { panic(err) } var b []byte b,err := ioutil.ReadAll(f)
|
ioutil
甚至封装了直接读取文件的函数:
1 2
| var b []byte b,err := ioutil.ReadFile("./my.dat")
|
3. 写入文件
要向文件写入内容,在调用OpenFile()
函数获得句柄时flag参数要传入O_WRONLY
或者O_RDWR
,如果是要往文件中以追加的形式在文件后面插入内容,还是需要O_APPEND
:
1
| OpenFile(name, O_RDWR|O_CREATE|O_APPEND, 0666)
|
os.File
有Write
方法,也就是说os.File
也实现了io.Writer
接口,所以同样可以调用fmt
、bufio
、ioutil
包将数据写入到文件中。
3.1 直接写入
写入文件最简单的方式就是调用os.File
类型的Write
方法写入一个字节数组:
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
| package main
import ( "fmt" "io" "os" ) func main() { file, err := os.OpenFile("./my.dat", os.O_RDWR|os.O_CREATE, 0666) if err != nil { panic(err) } defer file.Close() file.WriteString("hello moka\n") file.Seek(0, 0) for { var a1, a2 string _, err := fmt.Fscanln(file, &a1, &a2) fmt.Println(a1, a2) if err == io.EOF { break } } }
|
3.2 使用bufio包写入文件
bufio
包的NewWriter
可以将一个io.Writer
包装为bufio.Writer
结构体,该结构体主要有以下几个方法可以将数据写入文件:
1 2 3 4
| func (b *Writer) Write(p []byte) (nn int, err error) func (b *Writer) WriteByte(c byte) error func (b *Writer) WriteRune(r rune) (size int, err error) func (b *Writer) WriteString(s string) (int, error)
|
bufio
包的写入是带有缓冲区的,也就是说当写入数据时,不是立刻写入到文件,而是写到内存缓冲区,最后调用Flush
方法才将数据写入文件。
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 26 27 28 29 30
| package main
import ( "bufio" "fmt" "io" "os" )
func main() { file, err:= os.OpenFile("./my.dat", os.O_RDWR, 0666) if err != nil { panic(err) } defer file.Close() writer := bufio.NewWriter(file)
writer.Write([]byte("hello mocha")) writer.Flush() file.Seek(0, 0) for { var a1, a2 string _, err := fmt.Fscanln(file, &a1, &a2) fmt.Println(a1, a2) if err == io.EOF { break } } }
|
3.3 使用fmt包写入文件
fmt
包以下三个函数可以将格式化的数据写入到一个io.Writer
:
1 2 3
| func Fprint(w io.Writer, a ...any) (n int, err error) func Fprintf(w io.Writer, format string, a ...any) (n int, err error) func Fprintln(w io.Writer, a ...any) (n int, err error)
|
使用fmt写入文件的示例:
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 26
| package main
import ( "fmt" "io" "os" )
func main() { file, err := os.OpenFile("./my.dat", os.O_RDWR, 0666) if err != nil { panic(err) } defer file.Close() fmt.Fprintf(file, "%s:%s", "username", "moka") file.Seek(0, 0) for { var a1, a2 string _, err := fmt.Fscanln(file, &a1, &a2) fmt.Println(a1, a2) if err == io.EOF { break } } }
|
4. 判断是否为目录
要判断文件是否为目录,在获得os.File
对象,可以调用该对象的Stat
方法,该返回返回一个实现了os.FileInfo
接口的对象:
1 2 3 4 5 6 7 8
| type FileInfo interface { Name() string Size() int64 Mode() FileMode ModTime() time.Time IsDir() bool Sys() any }
|
比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main import ( "fmt" "os" ) func main() { filePath := "/path/to/file" fileInfo, err := os.Stat(filePath) if err != nil { fmt.Println("无法获取文件信息:", err) return } if fileInfo.IsDir() { fmt.Println("文件是一个目录") } else { fmt.Println("文件不是一个目录") } }
|
5. 遍历目录
可以调用os.ReadDir()
函数,该函数返回一个元素类型为os.DirEntry
的切片:
1
| func (f *File) ReadDir(n int) ([]DirEntry, error)
|
os.DirEntry
是一个接口,其定义如下:
1 2 3 4 5 6
| type DirEntry interface { Name() string IsDir() bool Type() FileMode Info() (FileInfo, error) }
|
DirEntry
接口也有IsDir()
方法,因为可以再往下遍历,下面是一个实现目录遍历的示例:
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 26 27
| package main
import ( "fmt" "log" "os" )
func main() { base := "./" IterateFolder(base) }
func IterateFolder(base string) { dirEntry, err := os.ReadDir(base) if err != nil { log.Fatal(err) } for _, v := range dirEntry { if v.IsDir() { IterateFolder(base + "/" + v.Name()) } else { fmt.Println(v.Name()) } } }
|
6. 修改文件名称
修改文件名称用os.Rename
函数:
1
| err := os.Rename("./demo1.txt", "./demo2.txt")
|
os.Rename
函数也可以用于移动文件:
1
| err := os.Rename("./hello.txt", "./m/hello.txt")
|
7. 删除文件
删除一个文件或者一个空目录,直接调用os
包的Remove()
函数即可:
1 2
| fileName := "./1.txt" os.Remove(fileName)
|
可以根据error
的返回值是否为nil判断是否删除成功,比如我们删除一个不存在的文件或者删除一个非空的目录:
1 2 3
| err := os.Remove("./m") fmt.Println(err)
|
执行结果:
1
| remove ./m: directory not empty
|
对于非空目录,如果要删除,可以用os
包的RemoveAll
函数:
1
| err := os.RemoveAll("./m")
|
参考
- https://juejin.cn/post/7264781288275394572
- https://juejin.cn/post/7260904509198762021?searchId=20230915030002D063C3D51EC1D87BE2B3#heading-0