区块链开发之Go语言—文件系统

时间:2022-05-07
本文章向大家介绍区块链开发之Go语言—文件系统,主要内容包括os — 平台无关的操作系统功能实现、截断文件、文件属性、目录与链接、path/filepath — 兼容操作系统的文件路径操作、相对路径和绝对路径、路径的切分和拼接、规整化路径、符号链接指向的路径名、文件路径匹配、遍历目录、参考、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

处理的文件名

  • 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

  • funcOpenFile(namestring,flagint,permFileMode)(*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 指定了文件的模式和权限位     //尴尬,这行排了好久排不好版,微信有bug。。
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)(nint,err error) Read 方法从 f 中读取最多 len(b) 字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取0个字节且返回值 err 为 io.EOF。
  • func(f*File)ReadAt(b[]byte,off int64)(nint,err error) ReadAt 从指定的位置(相对于文件开始位置)读取长度为 len(b) 个字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。当 n<len(b) 时,本方法总是会返回错误;如果是因为到达文件结尾,返回值err="" 会是="" io.eof。<="" li="" style="box-sizing: border-box;">
  • Read 和 ReadAt 的区别:前者从文件当前偏移量处读,且会改变文件当前的偏移量;而后者从 off 指定的位置开始读,且不会改变文件当前偏移量。

数据写入文件:Write

  • func(f*File)Write(b[]byte)(nint,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 返回错误:
    1. 关闭一个未打开的文件;
    2. 两次关闭同一个文件;
  • 通常,我们不回去检查 Close 的错误。

改变文件偏移量:Seek

文件打开时,会将文件偏移量设置为指向文件开始,以后每次 Read 或 Write 调用将自动对其进行调整,以指向已读或已写数据后的下一个字节。因此,连续的 Read 和 Write 调用将按顺序递进,对文件进行操作。

  • func(f*File)Seek(offset int64,whenceint)(ret int64,err error)Seek 可以调整文件偏移量
    • Seek 设置下一次读/写的位置。
    • offset 为相对偏移量,
    • whence 决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。使用中,whence 应该使用 os 包中的常量:SEEKSET、SEEKCUR 和 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)所指代文件的相关信息。

改变文件时间戳

  • funcChtimes(namestring,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。

目录与链接

创建和移除(硬)链接

硬链接是针对文件而言的,目录不允许创建硬链接。

  • funcLink(oldname,newnamestring)error

Link 创建一个名为 newname 指向 oldname 的硬链接。如果出错,会返回 *LinkError 类型的错误。

  • funcRemove(namestring)error

Remove 删除 name 指定的文件或目录。如果出错,会返回 *PathError 类型的错误。如果目录不为空,Remove 会返回失败。

更改文件名

funcRename(oldpath,newpathstring)error Rename 修改一个文件的名字或移动一个文件。如果 newpath 已经存在,则替换它。注意,可能会有一些个操作系统特定的限制。

使用符号链接

funcSymlink(oldname,newnamestring)error

Symlink 创建一个名为 newname 指向 oldname 的符号链接。如果出错,会返回 *LinkError 类型的错误。

有时候,我们希望通过符号链接,能获取其所指向的路径名。系统调用 readlink 能做到,Go 的封装函数是 os.Readlink:

funcReadlink(namestring)(string,error)

创建和移除目录

  • funcMkdir(namestring,permFileMode)error Mkdir 使用指定的权限和名称创建一个目录。如果出错,会返回 *PathError 类型的错误。
  • 因为 Mkdir 所创建的只是路径名中的最后一部分,如果父目录不存在,创建会失败。os.MkdirAll 用于递归创建所有不存在的目录。
  • funcRemove(namestring)error 移除一个指定的目录,目录可以是绝对路径或相对路径。
  • funcRemoveAll(pathstring)error RemoveAll 删除 path 指定的文件,或目录及它包含的任何下级对象。它会尝试删除所有东西,除非遇到错误并返回。如果 path 指定的对象不存在,RemoveAll 会返回 nil 而不返回错误。

读目录

func(f*File)Readdirnames(nint)(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(nint)(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() 函数将一个路径名字符串分解成文件名。函数返回路径的最后一个元素。在提取元素前会去掉末尾的斜杠。如果路径是"",会返回".";如果路径是只有一个斜杆构成的,会返回"/"。
  • funcExt(pathstring)string Ext 函数返回 path 文件扩展名。扩展名是路径中最后一个从 . 开始的部分,包括 .。如果该元素没有 . 会返回空字符串。

相对路径和绝对路径

  • funcIsAbs(pathstring)bool 返回路径是否是一个绝对路径
  • funcAbs(pathstring)(string,error) Abs 函数返回 path 代表的绝对路径,如果 path 不是绝对路径,会加入当前工作目录以使之成为绝对路径。
  • funcRel(basepath,targpathstring)(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>

路径的切分和拼接

  • funcSplit(pathstring)(dir,filestring) Split 函数根据最后一个路径分隔符将路径 path 分隔为目录和文件名两部分(dir 和 file)
  • funcJoin(elem...string)string Join 用于将多个路径拼接起来,会根据情况添加路径分隔符。
  • funcSplitList(pathstring)[]string SplitList 分割 PATH 或 GOPATH 之类的环境变量(这些路径被特定于OS 的列表分隔符连接起来)

规整化路径

  • funcClean(pathstring)string 通过单纯的词法操作返回和 path 代表同一地址的最短路径。

符号链接指向的路径名

  • funcEvalSymlinks(pathstring)(string,error) path 或返回值是相对路径,则是相对于进程当前工作目录。

文件路径匹配

  • funcGlob(patternstring)(matches[]string,err error) Glob 函数返回所有匹配了 模式字符串 pattern 的文件列表或者nil(如果没有匹配的文件)。Glob 的常见用法,是读取某个目录下所有的文件,比如写单元测试时,读取 testdata 目录下所有测试数据: filepath.Glob("testdata/*.input")

遍历目录

  • funcWalk(rootstring,walkFnWalkFunc)error Walk 函数会遍历 root 指定的目录下的文件树,对每一个该文件树中的目录和文件都会调用 walkFn,包括 root 自身。所有访问文件/目录时遇到的错误都会传递给 walkFn 过滤。文件是按字典顺序遍历的,这让输出更漂亮,但也导致处理非常大的目录时效率会降低。Walk 函数不会遍历文件树中的符号链接(快捷方式)文件包含的路径。
  • typeWalkFuncfunc(pathstring,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]( https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter01/01.0.html)