go的io与文件

背景

整理文件与io的关系,
go中各种读取文件的方式的对比.

介绍

文件在打开之前,可以看作一个与os交互的对象,
可以创建,删除,移动,设置权限等.

文件打开时,也是一个与os交互的对象.
根据文件的权限,读取的方式等等,写的方式等等,
对文件的操作会有不同的效果和反馈.

而文件打开后就变成一个I/O的reader或writer,
单纯一些,无非就是

  1. 读到指定buffer中
  2. 返回一个buffer
  3. 读取到字符串

亦或是

  1. 读整个文件
  2. 按行

文件和文件夹的常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 以创建文件为例
newFile, err := os.Create("1.txt") // 若已存在会清空文件内容
checkErr(err)
defer newFile.Close()
fmt.Println(newFile) // 成功打开会返回一个内存地址

// 其他操作
err = os.Remove("1.txt") // rm,没有文件,没有权限会报错
err = os.Mkdir("temp", 0755) // mkdir
err = os.MkdirAll("test1/temp", 0755) // mkdir -p
// err = os.Remove("test1") // rmdir,如果目录内部有内容,报错
err = os.Remove("temp") // rmdir
err = os.RemoveAll("test1") // rm -rf
err = os.Rename("test", "test2") // mv 可以操作文件或文件夹
fi, err := os.Stat("1.gob") // state,返回fileInfo中包含与stat命令类似的结果,路径有问题会报错
fmt.Println(fi)
os.Chmod("1.gob", 0440) // chmod
os.Chown("1.gob", os.Getuid(), os.Getgid()) // chown
// os.Symlink(oldname string, newname string) // ln -s
// os.Link(oldname string, newname string) // ln

文件的读写权限

OpenFile

1
file, err := os.OpenFile("1.txt", 读写模式, 权限)

文件打开时的常见错误:

  1. no such file or directory
  2. permission denied

文件指针

  1. 成功时返回指针
  2. 失败返回nil

读写模式

  1. O~RDONLY~ 只读(与只写冲突时优先只写)
  2. O~WRONLY~ 只写(会从开头开始写)
  3. O~APPEND~ 附加到尾部(相当于只写的扩展版,没有写权限时不生效)
  4. O~RDWR~ 读写(一个比较常用的选项,默认会在尾部附加内容)
  5. O~CREATE~ 不存在时将创建新的文件
  6. O~EXCL~ 与O~CREATE配合~,文件已经存在时报错
  7. O~SYNC~ 同步IO
  8. O~TRUNC~ 如果可能,打开时清空文件

读写模式可以使用 | 分隔,组合使用.

权限

权限通常只会在有 os.O_CREATE 时被使用.
文件权限在go看来是一个8进制的数字,因为最大到7,写法上比如 0755.

os包提供了许多关键字,其中只有 os.ModePerm 与文件有关,值等效于 0777,
其他的比如设备权限 os.ModeDeviceD---------.
因此如果创建文本文件,通常手动写 0644 会是一个合适的选择.

注意即使权限是0777,也会创建出0755的文件,因为受到系统的限制.
使用 umask -S 查看系统设置的权限上限, 使用 umask 可以得到 umask -S 的掩码.

Open

是一个快捷方式,底层为

1
OpenFile(name, O_RDONLY, 0)

io包

io包提供了对IO操作的最基本接口,基本考虑了操作IO时的各种情况.
包含了4大类方法和接口,这些类别中的每种接口都为不同的需求服务.互不相关
比如Reader和ReaderAt完全没有关系.

有些接口比如Reader,或使用函数(比如使用MultiReader),
或转为类型(比如转为LimitedReader),能够完成更多的功能.

    1. Reader(Read)
      1. LimitedReader(Read)
      2. PipeReader(Read, Close, CloseWithError)
      3. TeeReader(Read)
      4. MulltiReader(Read)
    2. ReaderAt(ReadAt)
      1. SectionReader(Size, Read, ReadAt, Seek)
    3. ByteReader(ReadByte)
    4. ByteScanner(ReadByte, UnreadByte)
    5. RuneReader(ReadRune)
    6. RuneScanner(ReadRune, UnreadRune)
    7. ReaderFrom(ReadFrom)
    1. Writer(Write)
      1. PipeWriter(Write, Close, CloseWithError)
      2. MultiWriter(Write)
    2. WriterAt(WriteAt)
    3. ByteWriter(WriteByte)
    4. WriterTo(WriteTo)
    1. Closer(Close)
    1. Seeker(Seek)

有时这四类接口还会相互组合,产生新的接口,从名字就能看出是哪些基本接口组合成的

  1. ReadCloser
  2. ReadSeeker
  3. WriteCloser
  4. WriteSeeker
  5. ReadWriteCloser
  6. ReadWriteSeeker

此外还有一些方便操作的函数

  1. ReadAtLeast(reader, buf, min) (n, err), 要求最少读取min字节,文件内容太短,buffer太小,都会报错
  2. ReadFull(reader, buf) (n, err), 要求正好填满buffer,文件内容太短会报错
  3. Copy(writer, reader) (n64, err) 从reader拷贝数据到writer
  4. CopyN(writer, reader, n) (n64, err) 从reader拷贝n字节数据到writer,数量不够会报错
  5. WriteString(writer, str) (n, err) 向writer写入字符串的是快捷方式
    否则就是 n, err = writer.Write([]byte(str))

os包中的File

File对象实现了一部分io包中的方法.

  1. Read
  2. ReadAt
  3. Write
  4. WriteAt
  5. WriteString
  6. Seek
  7. Close

诸如ReadByte等方法,需要借助其他包来对reader进行转换才能操作.
另外os包还提供简便的读写文件内容的方法

  1. os.ReadFile(filename)
  2. os.WriteFile(filename, data, 权限), 默认读写模式是 O_WRONLY|O_CREATE|O_TRUNC
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
31
32
33
34
35
36
37
38
39
40
41
42
43
func main() {
file, err := os.Open("1.txt")
if err != nil {
fmt.Println("err in open file", err)
}
defer file.Close()

// 1. Read读取全部
data := make([]byte, 100)
n, err := file.Read(data)
fmt.Println(data[:n])
fmt.Println("----------------------------------------")

// 2. ReadAt从设定的offset开始读取
n, err = file.ReadAt(data, 2)
fmt.Println(data[:n])
fmt.Println("----------------------------------------")

// 3. Seek设定读取位置,然后读取,效果上与ReadAt等效
file.Seek(3, 0)
n, err = file.Read(data)
fmt.Println(data[:n])
fmt.Println("----------------------------------------")

// 4. ReadFrom
f2, _ := os.OpenFile("2.txt", os.O_CREATE|os.O_WRONLY, 0644)
defer f2.Close()

// Read和ReadAt总是从头开始
// ReadFrom对当前读取位置敏感,因此需要重新Seek定位
file.Seek(0, 0)
n64, err := f2.ReadFrom(file)
fmt.Println(n64, err)

f2.Write([]byte("write\n"))
// offset是严格的,数字太大,能够造成两段文字之间有空白的非法字符
// cat命令看不出来,emacs或bat才可以
f2.WriteAt([]byte("write at\n"), 27)
f2.WriteString("write string")

buf, err := os.ReadFile("2.txt")
fmt.Println(string(buf), err)
}

ioutil包

主要用于解决一些io操作中的麻烦.

  1. Read函数需要自定义一个固定大小的buffer,然后函数返回n,最后使用 buffer[:n] 来获取有效信息.
    ioutil.ReadAll(reader) 方法返回一个合适大小,装有内容的buffer.
  2. 先打开文件再读取文件内容很麻烦.
    ioutil.Readfile(filename) 一步到位(事实上仅仅是用了 os.ReadFile(filename))
  3. 先打开文件在写入内容也很麻烦.
    ioutil.WriteFile(filename, data, permission) 一步到位(事实上也是用了os包的内容)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
// 先打开再读取
file, _ := os.Open("1.txt")
defer file.Close()
// 直接读取到合适长度的buffer中
buf1, _ := ioutil.ReadAll(file)
fmt.Println(buf1)

// 两步并作一步
buf2, _ := ioutil.ReadFile("1.txt")
fmt.Println(buf2)

// 写文件也并作一步
err := ioutil.WriteFile("2.txt", []byte("1\n2\n"), 0640)
fmt.Println(err)
}

bufio包

主要功能

  1. 将io包中的各种接口,统一到几个结构体上.
    1. Reader
      拥有Read,ReadByte,ReadRune等等方法.
    2. Writer
      拥有Write,WriteByte,WriteString等等方法
    3. ReadWriter
    4. Scanner
      (Reader的分化,强调文本与文本之间的分隔,比如换行和空格在[]byte看来就是数字,不确定意义,而Scanner看来无意义)
      拥有Split,Scan,Bytes,Text,Err等方法
  2. 为这些结构体带上多种内部字段,以及特殊的函数,用于各种目的
    1. buffer用于临时保存数据
    2. err用于存放错误
    3. 读写位置的offset
    4. Writer的Flush方法专门用于将数据写入io

可见简单操作文件可以使用ioutil,
而要实现对文件的丰富操作,离不开bufio包.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func main() {
file, _ := os.Open("1.txt")
defer file.Close()
reader := bufio.NewReader(file)

// 1. Read,支持最基本最原始的Read方法,会全部读取
buf := make([]byte, 100)
n, _ := reader.Read(buf)
fmt.Println(buf[:n])

// 2. 字节级别 ReadByte
file.Seek(0, 0) // read会记录位置,因此用seek会恢复一下
for {
_, err := reader.ReadByte()
if err == nil {
// fmt.Println(b)
} else if err == io.EOF {
break
} else {
log.Panicln(err)
}
}

file.Seek(0, 0)
// 3. 字符级别 ReadRune
r, size, err := reader.ReadRune()
fmt.Println(r, size, err)
err = reader.UnreadRune()
fmt.Println(err)

// 4.1 自定义级别,可以是行,返回[]byte
bs, err := reader.ReadSlice('\n')
fmt.Println(bs, err)

// 4.2 自定义级别,可以是行,返回[]byte,返回的是缓冲的拷贝
bs, err = reader.ReadBytes('\n')
fmt.Println(bs, err)

// 4.3 自定义级别,可以是行,返回字符串
str, err := reader.ReadString('\n')
fmt.Println(str, err)

// 5. 行级别
bs, isPrefix, err := reader.ReadLine()
fmt.Println(bs, isPrefix, err)
}

Writer同理,暂略

参考

  1. 官方文档的中文翻译,虽然有点旧