16.目录与文件
16.目录与文件任何编程语言在运行时都依赖于操作系统,所以程序在运行时会对系统文件、目录等执行一些相应操作,例如文件读写、执行系统命令、创建新的目录等。在Go语言中,可以使用内置包os实现了多种操作系统的操作指令,如主机、用户、进程、环境变量、目录与文件操作、终端执行等。
16.1 路径操作
存储设备保存着各种各样的数据,但需要一种方便快捷的模式让用户可以快速定位到相应数据资源的位置,而操作系统则提供一种基于路径字符串的表达式来解决此类问题,它像一棵倒置的层级目录树,从根开始。
[*]相对路径:不是以根目录开始的路径,例如:../xx、./xx
[*]绝对路径:以斜杆做为开始的路径,例如:/home/、/var/log、C:\windows
[*]路径分隔符:不同的操作系统的路径分隔符也不太一样,Windows的分隔符为 \,而Linux系统则为 /
为了方便处理,Go语言标准库提供path包和path/filepath来处理路径问题。而path/filepath是path的扩展,使用起来更加方便,一般使用path/filepath即可
16.1.1 路径拼接
由于路径是字符串,直接使用字符串拼接即可完成,另外也可以使用join方法,示例如下所示:
package main
import (
"fmt"
"path/filepath"
)
func main() {
pathA := "C:" + "\\surpass" + "\\" + "data"
pathB := filepath.Join("C:", "\\surpass", "\\", "data")
fmt.Printf("pathA: %+v\n", pathA)
fmt.Printf("pathB: %+v\n", pathB)
} 代码运行结果如下所示:
pathA: C:\surpass\data
pathB: C:\surpass\data16.1.2 路径分解
在一些场景下,我们仅需要路径中的一部分数据,这时候则可以使用路径分解功能,示例代码如下所示:
package main
import (
"fmt"
"path/filepath"
)
func main() {
path := filepath.Join("C:", "\\surpass", "\\", "data", "\\surpass.json")
// 拆解出路径和文件名
dir, file := filepath.Split(path)
fmt.Printf("路径为:%+v,文件名为:%+v\n", dir, file)
// 功能类似于dirname 和 basename
fmt.Printf("路径为:%+v,文件名为:%+v,扩展名为:%+v\n", filepath.Dir(path), filepath.Base(path), filepath.Ext(path))
} 代码运行结果如下所示:
路径为:C:\surpass\data\,文件名为:surpass.json
路径为:C:\surpass\data,文件名为:surpass.json,扩展名为:.json16.1.3 其他功能
示例代码如下所示:
package main
import (
"fmt"
"path/filepath"
)
func main() {
pathA := filepath.Join("C:", "\\surpass", "\\", "data", "\\surpass.json")
pathB := filepath.Join("..", "\\surpass", "\\data")
// 判断是否为绝对路径
fmt.Printf("pathA是否为绝对路径:%+v,pathB是否为绝对路径:%+v\n", filepath.IsAbs(pathA), filepath.IsAbs(pathB))
// 在使用Clean函数后,返回等价的最短路径
// 清除当前路径中的 . 和 ..
// 使用Join方法时,会自动调用Clean方法
pathC := "\\surpass\\test\\data\\" + "..\\.." + "\\test\\" + "\\code"
pathD := filepath.Join("\\surpass\\test\\data\\", "..\\..", "\\test", "\\code")
fmt.Printf("原始pathC:%+v,调用Clean方法之后:%+v\n", pathC, filepath.Clean(pathC))
fmt.Printf("调用Join方法之后pathD:%+v\n", pathD)
// 路径匹配
pathE := filepath.Join("data-1")
// 匹配0个或多个非 / 字符
if matchE, err := filepath.Match("*", pathE); err == nil {
fmt.Printf("pathE: %+v,匹配结果:%+v\n", pathE, matchE)
}
if matchF, err := filepath.Match("data-", pathE); err == nil {
fmt.Printf("pathE: %+v,匹配结果:%+v\n", pathE, matchF)
}
} 代码运行结果如下所示:
pathA是否为绝对路径:true,pathB是否为绝对路径:false
原始pathC:\surpass\test\data\..\..\test\\code,调用Clean方法之后:\surpass\test\code
调用Join方法之后pathD:\surpass\test\code
pathE: data-1,匹配结果:true
pathE: data-1,匹配结果:true16.2 目录操作
16.2.1 常规操作
示例代码如下所示:
package main
import (
"fmt"
"os"
)
func main() {
// 获取当前目录
if curPath, err := os.Getwd(); err == nil {
fmt.Printf("当前目录:%+v\n", curPath)
}
// 获取当前家目录
if userDir, err := os.UserHomeDir(); err == nil {
fmt.Printf("家目录:%+v\n", userDir)
}
// 切换目录路径
os.Chdir("F:\\")
if curPath, err := os.Getwd(); err == nil {
fmt.Printf("当前目录:%+v\n", curPath)
}
// 创建目录或多级目录
dirnameA := "F:\\testA"
dirnameB := "F:\\testA\\surpassA\\surpassB"
oldDirnameC := "F:\\testA\\surpassC"
newDirnameC := "F:\\testA\\surpassC-NewDirName"
os.Mkdir(dirnameA, os.ModePerm) // 相当于mkdir,要求父路径都已经存在,才能创建目录成功,否则报错
os.MkdirAll(dirnameB, os.ModePerm) // 相当于mkdir -p
os.MkdirAll(oldDirnameC, os.ModePerm)
// 判断目录是否存在
if _, err := os.Stat(dirnameB); err == nil {
fmt.Printf("路径: %+v 存在\n", dirnameB)
}
_, err := os.Stat(dirnameB)
fmt.Printf("文件是否不存在?%+v\n", os.IsNotExist(err))
// 文件夹重命名
os.Rename(oldDirnameC, newDirnameC)
// 删除目录
os.Remove(dirnameB) // 相当于 rm -f
os.RemoveAll(dirnameA) // 相当于rm -rf
if _, err := os.Stat(dirnameB); err != nil {
fmt.Printf("路径: %+v 不存在\n", dirnameB)
}
// 判断目录是否存在
_, err = os.Stat(dirnameB)
fmt.Printf("文件是否不存在?%+v\n", os.IsNotExist(err))
} 在创建目录时,注意事项如下所示:
[*]os.Mkdir() :相当于mkdir,要求父路径都已经存在,才能创建目录成功,否则报错
[*]os.MkdirAll() :相当于mkdir -p
[*]os.Remove() :相当于 rm -f
[*]os.RemoveAll():相当于rm -rf
代码运行结果如下所示:
当前目录:c:\Users\Surpass\Documents\GolangProjets\src\go-learning-note\02-代码\16\1605-目录操作
家目录:C:\Users\Surpass
当前目录:F:\
路径: F:\testA\surpassA\surpassB 存在
文件是否不存在?false
路径: F:\testA\surpassA\surpassB 不存在
文件是否不存在?true16.2.2 目录遍历
在日常开发过程,目录遍历是非常重要的,而遍历目录又可以分为遍历当前目录和递归遍历目录,可以使用以下方法对目录进行遍历,示例代码如下所示:
[*]目录结构:
Test/
├── data-1
│ ├── data-11
│ │ └── data-11.json
│ └── data-12
├── data-2
│ └── data-21
│ └── data-21.txt
└── test.txt
[*]目录遍历代码
package main
import (
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
)
func main() {
dir := "F:\\Test"
fmt.Println("仅遍历当前目录,但不递归-使用os.ReadDir")
// 仅遍历当前目录,但不递归
if de, err := os.ReadDir(dir); err != nil {
fmt.Printf("遍历目录%+v出错,错误信息:%+v\n", dir, err)
} else {
for i, v := range de {
fmt.Printf("索引%+v - 名字: %+v 是否为文件夹: %+v\n", i, v.Name(), v.IsDir())
}
}
fmt.Println("仅遍历当前目录,但不递归-使用ioutil.ReadDir")
// 仅遍历当前目录,但不递归,但推荐使用os.ReadDir()
if fileInfo, err := ioutil.ReadDir(dir); err != nil {
fmt.Printf("遍历目录%+v出错,错误信息:%+v\n", dir, err)
} else {
for i, v := range fileInfo {
fmt.Printf("索引%+v - 名字: %+v 是否为文件夹: %+v\n", i, v.Name(), v.IsDir())
}
}
fmt.Println("递归遍历目录-使用filepath.WalkDir")
// 递归遍历目录,且包含自身
filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
fmt.Printf("路径: %+v 文件或文件夹: %+v\n", path, d.Name())
return err
})
fmt.Println("递归遍历目录-使用filepath.Walk")
filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
fmt.Printf("路径: %+v 文件或文件夹: %+v\n", path, info.Name())
return err
})
} 代码运行结果如下所示:
仅遍历当前目录,但不递归-使用os.ReadDir
索引0 - 名字: data-1 是否为文件夹: true
索引1 - 名字: data-2 是否为文件夹: true
索引2 - 名字: test.txt 是否为文件夹: false
仅遍历当前目录,但不递归-使用ioutil.ReadDir
索引0 - 名字: data-1 是否为文件夹: true
索引1 - 名字: data-2 是否为文件夹: true
索引2 - 名字: test.txt 是否为文件夹: false
递归遍历目录-使用filepath.WalkDir
路径: F:\Test 文件或文件夹: Test
路径: F:\Test\data-1 文件或文件夹: data-1
路径: F:\Test\data-1\data-11 文件或文件夹: data-11
路径: F:\Test\data-1\data-11\data-11.json 文件或文件夹: data-11.json
路径: F:\Test\data-1\data-12 文件或文件夹: data-12
路径: F:\Test\data-2 文件或文件夹: data-2
路径: F:\Test\data-2\data-21 文件或文件夹: data-21
路径: F:\Test\data-2\data-21\data=21.txt 文件或文件夹: data=21.txt
路径: F:\Test\test.txt 文件或文件夹: test.txt
递归遍历目录-使用filepath.Walk
路径: F:\Test 文件或文件夹: Test
路径: F:\Test\data-1 文件或文件夹: data-1
路径: F:\Test\data-1\data-11 文件或文件夹: data-11
路径: F:\Test\data-1\data-11\data-11.json 文件或文件夹: data-11.json
路径: F:\Test\data-1\data-12 文件或文件夹: data-12
路径: F:\Test\data-2 文件或文件夹: data-2
路径: F:\Test\data-2\data-21 文件或文件夹: data-21
路径: F:\Test\data-2\data-21\data=21.txt 文件或文件夹: data=21.txt
路径: F:\Test\test.txt 文件或文件夹: test.txt16.3 文件操作
16.3.1 常规操作
这里的常规操作是指文件的创建、判断是否存在、重命名、修改权限、属主属组等操作,示例操作如下所示:
package main
import (
"fmt"
"os"
"path/filepath"
"time"
)
func main() {
oldFilename := "F:\\Test\\surpass.json"
newFilename := "F:\\Test\\surpass.json.rename"
//如果文件不存在,则创建相应的目录和文件
//如果相应的目录存在,则不创建目录
if _, err := os.Stat(oldFilename); os.IsNotExist(err) {
fmt.Printf("文件%+v不存在\n", oldFilename)
dirName := filepath.Dir(oldFilename)
if err := os.MkdirAll(dirName, os.ModePerm); err != nil {
fmt.Printf("创建目录%+v出错,错误信息:%+v\n", dirName, err)
return
}
// 文件创建
if f, err := os.Create(oldFilename); err == nil {
defer f.Close()
fmt.Printf("文件%+v创建成功\n", oldFilename)
} else {
fmt.Printf("文件%+v创建失败,错误信息:%+v\n", oldFilename, err)
}
}
// 文件重命名
// Windows里面注意重命名时,是否有其他进程打开或占用该文件
if err := os.Rename(oldFilename, newFilename); err != nil {
fmt.Printf("重命名文件%+v失败,错误信息:%+v\n", oldFilename, err)
}
// 修改文件权限,一般适用于Linux
if err := os.Chmod(newFilename, 0777); err != nil {
fmt.Printf("修改文件权限%+v失败,错误信息:%+v\n", newFilename, err)
}
// 修改访问和修改时间
now := time.Now().Add(time.Hour * -2)
if err := os.Chtimes(newFilename, now, now); err != nil {
fmt.Printf("修改文件时间%+v失败,错误信息:%+v\n", newFilename, err)
}
// 修改文件属主和属组,适用于Linux,且需要注意权限,Windows不支持该方法
if err := os.Chown(newFilename, 0, 0); err != nil {
fmt.Printf("修改属主和属组时间%+v失败,错误信息:%+v\n", newFilename, err)
}
// 删除单个文件
if err := os.Remove(newFilename); err != nil {
fmt.Printf("删除文件%+v失败,错误信息:%+v\n", newFilename, err)
}
if err := os.RemoveAll(filepath.Dir(newFilename)); err != nil {
fmt.Printf("删除文件夹%+v失败,错误信息:%+v\n", filepath.Dir(newFilename), err)
}
}16.3.2 文件读写
文件本质上就是存储在存储设备上,并且经过序列化的二进制数据,如果我们需要对一个文件进行读写操作,一般可以拆分为找到文件、打开文件、执行操作(读写) 等几个步骤。
[*]在打开文件会涉及到很多的读写模式,例如只读、只写、追加等模式
[*]读取文件本质上可以理解为如何读取字节数组中的数据
[*]写文件本质上可以理解为向字节数组中写入新的元素或者覆盖某些元素
16.3.2.1 文件打开与读取
在Go语言中打开文件常常使用os模块中的Open和OpenFile方法。示例代码如下所示:
package main
import (
"fmt"
"os"
)
func main() {
// 文件内容为:Surpass
filename := "F:\\Test\\surpass.txt"
// 以只读方式打开文件
f, err := os.Open(filename)
if err != nil {
fmt.Printf("打开文件%+v出错,错误信息:%+v\n", filename, err)
panic(err)
}
defer f.Close()
// 读取文件
var buffer = make([]byte, 3)
for {
// 成功读取n字节
n, err := f.Read(buffer)
if err != nil {
// 文件读到结尾时,EOF n=0
fmt.Printf("读取文件出错:%+v\n", err)
break
}
// 注意事项:
// 因为每次读取3个字节,直到文件结尾
// 但由于buffer未做清空处理,当读到文件末尾时,如果不够3个字节时,
// 则仅拿当前已经读取到内容替换相应位置的字节
// 而前一次的内容因未替换,也会输出,导致存在数据污染
fmt.Printf("从%+v成功读取到%+v个字节,转换后结果%+v,未使用切片处理的结果:%+v\n", buffer[:n], n, string(buffer[:n]), buffer)
}
} 代码运行结果如下所示:
从成功读取到3个字节,转换后结果Sur,未使用切片处理的结果:
从成功读取到3个字节,转换后结果pas,未使用切片处理的结果:
从成功读取到1个字节,转换后结果s,未使用切片处理的结果:
读取文件出错:EOF16.3.2.2 文件带定位读取
通过前面文件打开可以看出,不管文件有多大,但在读取时都是一点一点读取到内存中,且该操作是从前向后依次读取的。这种方式也称之为顺序读取,但若现在有一个超大的文件,只需要读取其中一段数据,该如何读取呢?
在前面,我们提到过文件实际是经过序列化的数据,既意味着文件可以看成是一个非常大的字节数组。读取操作实际上是从字节数组里面获取部分或全部数据。因此,读取文件可以理解为,有一个指针指向特定的位置,其随着指针不断向后移动,从而实现读取数据的功能。既然这样,那我们在读取文件的时候,提前指定指针的位置,就可以读取任意位置的数据了,而在Go语言中提供了Seek方法来实现该功能,其用法如下所示:
Seek(offset int64, whence int) (ret int64, err error) whence功能如下所示:
[*]whence=0:相对于文件开头位置,offset只能为正,否则报错
[*]whence=1:相对于文件当前位置,offset可以为正,也可以为负,但在为负时,不能超过左边界,即文件开头
[*]whence=2:相对于文件结尾位置,offset可以为正,也可以为负,但在为负时,不能超过左边界,即文件开头
特殊的用法如下所示:
// 将文件指针移动至文件开头
Seek(0,0)
// 将文件指针移动到文件末尾
Seek(0,2) 示例代码如下所示:
package main
import (
"fmt"
"os"
)
func main() {
// 文件内容为:SurpassGoTestFileSeek
filename := "F:\\Test\\surpass.txt"
// 以只读方式打开文件
if f, err := os.Open(filename); err == nil {
defer f.Close()
buffer := make([]byte, 5)
var n int
if n, err = f.Read(buffer); err != nil {
fmt.Printf("读取文件:%+v出错,错误信息:%+v\n", filename, err)
}
fmt.Printf("文件已经读取到位置:%v,内容为:%+v\n", n, string(buffer[:n]))
// seek
// whence=0 文件开头
// whence=1 当前位置
// whence=2 文件结尾
ret, _ := f.Seek(2, 0)
fmt.Printf("文件当前偏移量为: %v\n", ret)
n, _ = f.Read(buffer)
fmt.Printf("使用seek,相对于文件头,文件%+v从%+v开始读取,结果为%+v\n", filename, ret, string(buffer[:n]))
// whence=1正偏移量
ret, _ = f.Seek(2, 1)
fmt.Printf("文件当前偏移量为: %v\n", ret)
n, _ = f.Read(buffer)
fmt.Printf("使用seek,相对于当前位置正偏移量,文件%+v从%+v开始读取,结果为%+v\n", filename, ret, string(buffer[:n]))
// whence=1负偏移量
ret, _ = f.Seek(-3, 1)
fmt.Printf("文件当前偏移量为: %v\n", ret)
n, _ = f.Read(buffer)
fmt.Printf("使用seek,相对于当前位置负偏移量,文件%+v从%+v开始读取,结果为%+v\n", filename, ret, string(buffer[:n]))
// whence=2 正偏移量,因为相对于文件末尾,再偏移是读取不到数据的
ret, _ = f.Seek(1, 2)
fmt.Printf("文件当前偏移量为: %v\n", ret)
n, _ = f.Read(buffer)
fmt.Printf("使用seek,相对于文件末尾正偏移量,文件%+v从%+v开始读取,结果为%+v\n", filename, ret, string(buffer[:n]))
// whence=2 负偏移量
ret, _ = f.Seek(-12, 2)
fmt.Printf("文件当前偏移量为: %v\n", ret)
n, _ = f.Read(buffer)
fmt.Printf("使用seek,相对于文件末尾负偏移量,文件%+v从%+v开始读取,结果为%+v\n", filename, ret, string(buffer[:n]))
// 另外类似于Seek的方法为ReadAt,偏移量相对于文件开头,但不影响当前文件指针
var offset int64 = 10
n, _ = f.ReadAt(buffer, offset)
fmt.Printf("使用ReadAt,相对于文件开头偏移,文件%+v从%+v开始读取,结果为%+v\n", filename, offset, string(buffer[:n]))
n, _ = f.Read(buffer)
fmt.Printf("使用Read,从文件%+v读取到的结果为%+v\n", filename, string(buffer[:n]))
}
} 文件运行结果如下所示:
文件已经读取到位置:5,内容为:Surpa
文件当前偏移量为: 2
使用seek,相对于文件头,文件F:\Test\surpass.txt从2开始读取,结果为rpass
文件当前偏移量为: 9
使用seek,相对于当前位置正偏移量,文件F:\Test\surpass.txt从9开始读取,结果为TestF
文件当前偏移量为: 11
使用seek,相对于当前位置负偏移量,文件F:\Test\surpass.txt从11开始读取,结果为stFil
文件当前偏移量为: 22
使用seek,相对于文件末尾正偏移量,文件F:\Test\surpass.txt从22开始读取,结果为
文件当前偏移量为: 9
使用seek,相对于文件末尾负偏移量,文件F:\Test\surpass.txt从9开始读取,结果为TestF
使用ReadAt,相对于文件开头偏移,文件F:\Test\surpass.txt从10开始读取,结果为estFi
使用Read,从文件F:\Test\surpass.txt读取到的结果为ileSe16.3.2.3 文件使用缓冲读取
使用Read读取,是非常偏向于底层的,但操作起来不太方便。在Go语言同时也提供了bufio包,实现了对文件的二进制或文本文件的处理方法。
如果要使用带buffer的方式读取文件,bufio.Reader构造函数为:
func NewReader(rd io.Reader) *Reader {
return NewReaderSize(rd, defaultBufSize)
} 在以上构造函数中,要求传入的参数为io.Reader,其定义的类型为接口类型,如下所示:
type Reader interface {
Read(p []byte) (n int, err error)
} 也就意味着,传入的参数需要实现接口Reader方法,而os.File实现了该接口的Read方法。默认为4096字节。示例代码如下所示:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// 文件内容为:Surpass测试GoTestFileSeek\n测试\n学习Go
filename := "F:\\Test\\surpass.txt"
f, err := os.Open(filename)
defer f.Close()
if err != nil {
fmt.Printf("读取文件错误: %v\n", err)
}
// 复制了f的内指针,相当自己再另外打开一次
reader := bufio.NewReader(f)
buffer := make([]byte, 5)
n, _ := reader.Read(buffer)
fmt.Printf("Read方法 - 读取文件%+v,读取了%+v字节,读取结果:%+v\n", filename, n, string(buffer[:n]))
// 按字节进行读取
b1, _ := reader.ReadByte()
fmt.Printf("ReadByte方法 - 读取文件%+v,读取结果:%+v\n", filename, string(b1))
// 这里调整文件指针无用,带buffer内部有自己一个文件指针
f.Seek(0, 0)
// 遇到分隔符后停止,同时包含分隔符,适用于需要按指定分隔符的读取操作,比例按行读取\n
b2, _ := reader.ReadBytes('s')
fmt.Printf("ReadBytes方法 - 读取文件%+v,读取结果:%+v\n", filename, string(b2))
// 适用于读取非英文字符的文件内容
r, size, _ := reader.ReadRune()
fmt.Printf("ReadRune方法 - 读取文件%+v,读取结果:%+v,读取长度:%+v\n", filename, r, size)
line, _ := reader.ReadSlice('\n')
fmt.Printf("ReadSlice方法 - 读取文件%+v,读取结果:%+v\n", filename, string(line))
str, _ := reader.ReadString('\n')
fmt.Printf("ReadString方法 - 读取文件%+v,读取结果:%+v\n", filename, str)
} 代码运行结果如下所示:
Read方法 - 读取文件F:\Test\surpass.txt,读取了5字节,读取结果:Surpa
ReadByte方法 - 读取文件F:\Test\surpass.txt,读取结果:s
ReadBytes方法 - 读取文件F:\Test\surpass.txt,读取结果:s
ReadRune方法 - 读取文件F:\Test\surpass.txt,读取结果:27979,读取长度:3
ReadSlice方法 - 读取文件F:\Test\surpass.txt,读取结果:试GoTestFileSeek
ReadString方法 - 读取文件F:\Test\surpass.txt,读取结果:测试16.3.2.4 文件打开Flag
我们来看看os.OpenFile的方法定义:
func OpenFile(name string, flag int, perm FileMode) (*File, error) OpenFile方法共有3个参数,其中参数flag为int类型,代表文件读写模式,参数perm是FileMode类型,代表文件的相应权限,默认为0或0666。而内置的os.Open定义如下所示:
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
} 通过其定义可以看出,也是基于OpenFile定义,而相应的参数Flag设置为O_RDONLY,代表以只读方式打开。在Go语言中,os默认定义了以下几种文件读写模式,且每一种都胡详细的说明,源码如下所示:
const (
// Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
// 以下三种模式为互斥模式
O_RDONLY int = syscall.O_RDONLY // open the file read-only.
O_WRONLY int = syscall.O_WRONLY // open the file write-only.
O_RDWR int = syscall.O_RDWR // open the file read-write.
// The remaining values may be or'ed in to control behavior.
// 追加写入模式
O_APPEND int = syscall.O_APPEND // append data to the file when writing.
// 文件不存在时则创建
O_CREATE int = syscall.O_CREAT// create a new file if none exists.
// 如果和O_CREATE一起使用,则要求文件必须不存在,否则则报错
O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist.
// 同步IO,等待前面一次IO完成后再进行
O_SYNC int = syscall.O_SYNC // open for synchronous I/O.
// 打开文件时,先清空再写入,即覆盖模式
O_TRUNCint = syscall.O_TRUNC// truncate regular writable file when opened.
)
[*]O_RDONLY、O_WRONLY、O_RDWR:单独使用,如果文件不存在会报错,即要求在读写文件时,该文件必须存在
[*]O_RDONLY:以只读模式打开,一般较少使用
[*]O_RDONLY | O_APPEND 等价于 O_RDWR | O_APPEND前者的写法存在歧义,推荐使用后者写法
文件权限的源码定义如下所示:
const ( // The single letters are the abbreviations // used by the String method's formatting. ModeDir FileMode = 1
页:
[1]