区块链开发之Go语言—文件系统
处理的文件名
- path库
- filepath库
查看文件的元信息
- os.Stat
- os.Lstat
操作临时文件区域
- os.TempDir
os — 平台无关的操作系统功能实现
os 封装了系统无关的实现。在实际编程中,我们应该总是优先使用 os 中提供的功能,而不是 syscall。
文件 I/O
了解IO需要参照Unix文件系统的概念。
- 在 Unix 系统调用中,所有执行 I/O 操作以文件描述符,一个非负整数(通常是小整数),来指代打开的文件。
- 文件描述符用以表示所有类型的已打开文件,包括管道(pipe)、FIFO、socket、终端、设备和普通文件。
- 在 Go 中,文件描述符封装在 os.File 结构中,通过 File.Fd() 可以获得底层的文件描述符:fd。
- 3 种标准的文件描述符:
- 0-标准输入;os.Stdin;对应Unix的/dev/stdin
- 1-标准输出;os.Stdout;对应Unix的/dev/stdout
- 2-标准错误;os.Stderr;对应Unix的/dev/stderr
打开一个文件:OpenFile
-
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 // 如果可能,打开时清空文件 )
- 参数 perm 指定了文件的模式和权限位
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权限位(用于通过&获取类型位))
读取文件内容:Read和ReadAt
-
func (f *File) Read(b []byte) (n int, err error)
Read 方法从 f 中读取最多 len(b) 字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取0个字节且返回值 err 为 io.EOF。 -
func (f *File) ReadAt(b []byte, off int64) (n int, err error)
ReadAt 从指定的位置(相对于文件开始位置)读取长度为 len(b) 个字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。当 n<len(b) 时,本方法总是会返回错误;如果是因为到达文件结尾,返回值err 会是 io.EOF。 - Read 和 ReadAt 的区别:前者从文件当前偏移量处读,且会改变文件当前的偏移量;而后者从 off 指定的位置开始读,且不会改变文件当前偏移量。
数据写入文件:Write
-
func (f *File) Write(b []byte) (n int, err error)
Write 向文件中写入 len(b) 字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值 n!=len(b),本方法会返回一个非nil的错误。 - 注意:Write 调用成功并不能保证数据已经写入磁盘,因为内核会缓存磁盘的 I/O 操作。如果希望立刻将数据写入磁盘(一般场景不建议这么做,因为会影响性能),有两种办法:
- 打开文件时指定 os.O_SYNC;
- 调用 File.Sync() 方法。
关闭文件:Close
-
func (f *File) Close() error
os.File.Close() 是对 close() 的封装。我们应该养成关闭不需要的文件的良好编程习惯。文件描述符是资源,Go 的 gc 是针对内存的,并不会自动回收资源,如果关闭文件描述符,长期运行的服务可能会把文件描述符耗尽。 -
以下两种情况会导致 Close 返回错误:
- 关闭一个未打开的文件;
- 两次关闭同一个文件;
- 通常,我们不回去检查 Close 的错误。
改变文件偏移量:Seek
文件打开时,会将文件偏移量设置为指向文件开始,以后每次 Read 或 Write 调用将自动对其进行调整,以指向已读或已写数据后的下一个字节。因此,连续的 Read 和 Write 调用将按顺序递进,对文件进行操作。
-
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek 可以调整文件偏移量- Seek 设置下一次读/写的位置。
- offset 为相对偏移量,
- whence 决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。使用中,whence 应该使用 os 包中的常量:SEEK_SET、SEEK_CUR 和 SEEK_END。
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 或 终端。
截断文件
func Truncate(name string, size int64) error
func (f *File) Truncate(size int64) error
丢弃文件中大于size的后续字节。
文件属性
- 文件属性,也即文件元数据。
- 文件属性具体信息通过 os.FileInfo 接口获取。函数 Stat、Lstat 和 File.Stat 可以得到该接口的实例。
- stat 会返回所命名文件的相关信息。
- lstat 与 stat 类似,区别在于如果文件是符号链接,那么所返回的信息针对的是符号链接自身(而非符号链接所指向的文件)。
- fstat 则会返回由某个打开文件描述符(Go 中则是当前打开文件 File)所指代文件的相关信息。
改变文件时间戳
-
func Chtimes(name string, atime time.Time, mtime time.Time) error
可以显示改变文件的访问时间和修改时间。
文件属主
每个文件都有一个与之关联的用户ID(UID)和组ID(GID),籍此可以判定文件的属主和属组。
func Chown(name string, uid, gid int) error
func Lchown(name string, uid, gid int) error
func (f *File) Chown(uid, gid int) error
文件权限
这里介绍是应用于文件和目录的权限方案,但其规则可适用于所有文件类型,包括设备文件、FIFO 以及 Unix 域套接字等。
普通文件的权限
- Owner(亦称为 user):授予文件属主的权限。
- Group:授予文件属组成员用户的权限。
- Other:授予其他用户的权限。
每一类用户授予的权限如下:- Read:可阅读文件的内容。
- Write:可更改文件的内容。
- Execute:可以执行文件(如程序或脚本)
目录权限
- 读权限:可列出(比如,通过 ls 命令)目录之下的内容(即目录下的文件名)
- 写权限:可在目录内创建、删除文件。注意,要删除文件,对文件本身无需有任何权限。
- 可执行权限:可访问目录中的文件。因此,有时也将对目录的执行权限称为 search(搜索)权限。
Sticky 位
一般用于目录,起限制删除位的作用。可以在多用户环境下的共享文件夹里删除自己各自的文件。
- os.Chmod 和 os.File.Chmod 可以修改文件权限(包括 sticky 位),分别对应系统调用 chmod 和 fchmod。
目录与链接
创建和移除(硬)链接
硬链接是针对文件而言的,目录不允许创建硬链接。
func Link(oldname, newname string) error
Link 创建一个名为 newname 指向 oldname 的硬链接。如果出错,会返回 *LinkError 类型的错误。
func Remove(name string) error
Remove 删除 name 指定的文件或目录。如果出错,会返回 *PathError 类型的错误。如果目录不为空,Remove 会返回失败。
更改文件名
func Rename(oldpath, newpath string) error
Rename 修改一个文件的名字或移动一个文件。如果 newpath 已经存在,则替换它。注意,可能会有一些个操作系统特定的限制。
使用符号链接
func Symlink(oldname, newname string) error
Symlink 创建一个名为 newname 指向 oldname 的符号链接。如果出错,会返回 *LinkError 类型的错误。
有时候,我们希望通过符号链接,能获取其所指向的路径名。系统调用 readlink 能做到,Go 的封装函数是 os.Readlink:
func Readlink(name string) (string, error)
创建和移除目录
-
func Mkdir(name string, perm FileMode) error
Mkdir 使用指定的权限和名称创建一个目录。如果出错,会返回 *PathError 类型的错误。 - 因为 Mkdir 所创建的只是路径名中的最后一部分,如果父目录不存在,创建会失败。os.MkdirAll 用于递归创建所有不存在的目录。
-
func Remove(name string) error
移除一个指定的目录,目录可以是绝对路径或相对路径。 -
func RemoveAll(path string) error
RemoveAll 删除 path 指定的文件,或目录及它包含的任何下级对象。它会尝试删除所有东西,除非遇到错误并返回。如果 path 指定的对象不存在,RemoveAll 会返回 nil 而不返回错误。
读目录
func (f *File) Readdirnames(n int) (names []string, err error)
Readdirnames 读取目录 f 的内容,返回一个最多有 n 个成员的[]string,切片成员为目录中文件对象的名字,采用目录顺序。对本函数的下一次调用会返回上一次调用未读取的内容的信息。
如果 n>0,Readdirnames 函数会返回一个最多 n 个成员的切片。这时,如果 Readdirnames 返回一个空切片,它会返回一个非 nil 的错误说明原因。如果到达了目录 f 的结尾,返回值 err 会是 io.EOF。
如果 n<=0,Readdirnames 函数返回目录中剩余所有文件对象的名字构成的切片。此时,如果 Readdirnames 调用成功(读取所有内容直到结尾),它会返回该切片和 nil 的错误值。如果在到达结尾前遇到错误,会返回之前成功读取的名字构成的切片和该错误。
-
func (f *File) Readdir(n int) (fi []FileInfo, err error)
Readdir 内部会调用 Readdirnames,将得到的 names 构造路径,通过 Lstat 构造出 []FileInfo。
path/filepath — 兼容操作系统的文件路径操作
路径分隔符使用 os.PathSeparator
解析路径名字符串
func Dir(path string) string
func Base(path string) string
-
Dir() 函数将一个路径名字符串分解成目录名。返回路径中除去最后一个路径元素的部分,即该路径最后一个元素所在的目录。在使用 Split 去掉最后一个元素后,会简化路径并去掉末尾的斜杠。如果路径是空字符串,会返回".";如果路径由1到多个斜杠后跟0到多个非斜杠字符组成,会返回"/";其他任何情况下都不会返回以斜杠结尾的路径。
-
Base() 函数将一个路径名字符串分解成文件名。函数返回路径的最后一个元素。在提取元素前会去掉末尾的斜杠。如果路径是"",会返回".";如果路径是只有一个斜杆构成的,会返回"/"。
-
func Ext(path string) string
Ext 函数返回 path 文件扩展名。扩展名是路径中最后一个从 . 开始的部分,包括 .。如果该元素没有 . 会返回空字符串。
相对路径和绝对路径
-
func IsAbs(path string) bool
返回路径是否是一个绝对路径 -
func Abs(path string) (string, error)
Abs 函数返回 path 代表的绝对路径,如果 path 不是绝对路径,会加入当前工作目录以使之成为绝对路径。 -
func Rel(basepath, targpath string) (string, error)
Rel 函数返回一个相对路径
fmt.Println(filepath.Rel("/home/polaris/studygolang", "/home/polaris/studygolang/src/logic/topic.go"))
fmt.Println(filepath.Rel("/home/polaris/studygolang", "/data/studygolang"))
// Output:
// src/logic/topic.go <nil>
// ../../../data/studygolang <nil>
路径的切分和拼接
-
func Split(path string) (dir, file string)
Split 函数根据最后一个路径分隔符将路径 path 分隔为目录和文件名两部分(dir 和 file) -
func Join(elem ...string) string
Join 用于将多个路径拼接起来,会根据情况添加路径分隔符。 -
func SplitList(path string) []string
SplitList 分割 PATH 或 GOPATH 之类的环境变量(这些路径被特定于OS 的列表分隔符连接起来)
规整化路径
-
func Clean(path string) string
通过单纯的词法操作返回和 path 代表同一地址的最短路径。
符号链接指向的路径名
-
func EvalSymlinks(path string) (string, error)
path 或返回值是相对路径,则是相对于进程当前工作目录。
文件路径匹配
-
func Glob(pattern string) (matches []string, err error)
Glob 函数返回所有匹配了 模式字符串 pattern 的文件列表或者nil(如果没有匹配的文件)。Glob 的常见用法,是读取某个目录下所有的文件,比如写单元测试时,读取 testdata 目录下所有测试数据:
filepath.Glob("testdata/*.input")
遍历目录
-
func Walk(root string, walkFn WalkFunc) error
Walk 函数会遍历 root 指定的目录下的文件树,对每一个该文件树中的目录和文件都会调用 walkFn,包括 root 自身。所有访问文件/目录时遇到的错误都会传递给 walkFn 过滤。文件是按字典顺序遍历的,这让输出更漂亮,但也导致处理非常大的目录时效率会降低。Walk 函数不会遍历文件树中的符号链接(快捷方式)文件包含的路径。 -
type WalkFunc func(path string, info os.FileInfo, err error) error
Walk 函数对每一个文件/目录都会调用 WalkFunc 函数类型值。调用时 path 参数会包含 Walk 的 root 参数作为前缀;就是说,如果 Walk 函数的 root 为 "dir",该目录下有文件 "a",将会使用 "dir/a" 作为调用 walkFn 的参数。walkFn 参数被调用时的 info 参数是 path 指定的地址(文件/目录)的文件信息,类型为 os.FileInfo。
参考
1.《Go语言标准库》The Golang Standard Library by Example
关于我:
linxinzhe,全栈工程师,目前供职于某500强通信企业,人工智能,区块链爱好者。
GitHub:https://github.com/linxinzhe
欢迎留言讨论,也欢迎关注我~
我也会关注你的哦!