Go 标准库学习——io相关
一般的,计算机程序是:输入 (Input) 经过算法处理产生输出 (Output)。各种语言一般都会提供IO库供开发者使用。Go语言也不例外。
Go 语言中,为了方便开发者使用,将 IO 操作封装在了如下几个包中:
- io 为 IO 原语(I/O primitives)提供基本的接口
- io/ioutil 封装一些实用的 I/O 函数
- fmt 实现格式化 I/O,类似 C 语言中的 printf 和 scanf
- bufio 实现带缓冲I/O
介绍这些 IO 包提供的函数、类型和方法,同时通过实例讲解这些包的使用方法。
io — 基本的io接口
Reader接口
1 | |
官方文档中关于该接口方法的说明:
Read 将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len(p)) 以及任何遇到的错误。即使 Read 返回的 n < len(p),它也会在调用过程中占用 len(p) 个字节作为暂存空间。若可读取的数据不到 len(p) 个字节,Read 会返回可用数据,而不是等待更多数据。
当 Read 在成功读取 n > 0 个字节后遇到一个错误或 EOF (end-of-file),它会返回读取的字节数。它可能会同时在本次的调用中返回一个non-nil错误,或在下一次的调用中返回这个错误(且 n 为 0)。 一般情况下, Reader会返回一个非0字节数n, 若 n = len(p) 个字节从输入源的结尾处由 Read 返回,Read可能返回 err == EOF 或者 err == nil。并且之后的 Read() 都应该返回 (n:0, err:EOF)。
调用者在考虑错误之前应当首先处理返回的数据。这样做可以正确地处理在读取一些字节后产生的 I/O 错误,同时允许EOF的出现。
该接口下的Read函数需要传入一个字节数组,读取完成之后就会返回读取的字节数和nil,该接口函数返回的err不是nil的时候可能是EOF。
io.EOF 变量的定义:
var EOF = errors.New("EOF"),是 error 类型。根据 reader 接口的说明,在 n > 0 且数据被读完了的情况下,返回的 error 有可能是 EOF 也有可能是 nil。
根据 Go 语言中关于接口和满足了接口的类型的定义(Interface_types),我们知道 Reader 接口的方法集(Method_sets)只包含一个 Read 方法,因此,所有实现了 Read 方法的类型都满足 io.Reader 接口,也就是说,在所有需要 io.Reader 的地方,可以传递实现了 Read() 方法的类型的实例。
下面,我们通过具体例子来谈谈该接口的用法。
1 | |
ReadFrom 函数将 io.Reader 作为参数,也就是说,ReadFrom 可以从任意的地方读取数据,只要来源实现了 io.Reader 接口。比如,我们可以从标准输入、文件、字符串等读取数据,示例代码如下:
1 | |
Writer接口
Writer 接口的定义如下:
1 | |
官方文档中关于该接口方法的说明:
Write 将 len(p) 个字节从 p 中写入到基本数据流中。它返回从 p 中被写入的字节数 n(0 <= n <= len(p))以及任何遇到的引起写入提前停止的错误。若 Write 返回的 n < len(p),它就必须返回一个 非nil 的错误。
同样的,所有实现了Write方法的类型都实现了 io.Writer 接口。
ReaderAt接口
ReaderAt 接口的定义如下:
1 | |
ReadAt 从基本输入源的偏移量 off 处开始,将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len(p))以及任何遇到的错误。
当 ReadAt 返回的 n < len(p) 时,它就会返回一个 非nil 的错误来解释 为什么没有返回更多的字节。在这一点上,ReadAt 比 Read 更严格。
即使 ReadAt 返回的 n < len(p),它也会在调用过程中使用 p 的全部作为暂存空间。若可读取的数据不到 len(p) 字节,ReadAt 就会阻塞,直到所有数据都可用或一个错误发生。 在这一点上 ReadAt 不同于 Read。
若 n = len(p) 个字节从输入源的结尾处由 ReadAt 返回,Read可能返回 err == EOF 或者 err == nil
若 ReadAt 携带一个偏移量从输入源读取,ReadAt 应当既不影响偏移量也不被它所影响。
可对相同的输入源并行执行 ReadAt 调用。
ReaderAt 接口使得可以从指定偏移量处开始读取数据。简单示例代码如下:
1 | |
以上实例化了6个字节大小的byte数组,调用函数ReadAt函数,传入数组以及偏移量2,又因为Un
icode中一个汉字字符占3个字节,所以就读取2个汉字,即标准;
ReaderFrom接口
ReaderFrom 的定义如下:
1 | |
ReadFrom 从 r 中读取数据,直到 EOF 或发生错误。其返回值 n 为读取的字节数。除 io.EOF 之外,在读取过程中遇到的任何错误也将被返回。
下面的例子简单的实现将文件中的数据全部读取(显示在标准输出):
1 | |
打开 writeAt.txt 文件,发挥 fileInfo 类型对象,该结构体已经实现了Reader和Writer接口;
bufio.NewWriter 函数需要传入一个实现了Writer接口的参数,这里传入了os包下的标准输出;该函数返回一个Writer指针;
该指针调用ReadFrom函数,传入file文件,表示从文件里面读取数据,调用Flush函数刷新页面。
当然,我们可以通过 ioutil 包的 ReadFile 函数获取文件全部内容。其实,跟踪一下 ioutil.ReadFile 的源码,会发现其实也是通过 ReadFrom 方法实现(用的是 bytes.Buffer,它实现了 ReaderFrom 接口)。
ioutils—方便的io操作函数
虽然 io 包提供了不少类型、方法和函数,但有时候使用起来不是那么方便。比如读取一个文件中的所有内容。为此,标准库中提供了一些常用、方便的IO操作函数。
ReadAll函数
很多时候,需要一次性读取 io.Reader 中的数据,考虑到读取所有数据的需求比较多,Go 提供了 ReadAll 这个函数,用来从io.Reader 中一次读取所有数据。
1 | |
阅读该函数的源码发现,它是通过 bytes.Buffer 中的 ReadFrom 来实现读取所有数据的。该函数成功调用后会返回 err == nil 而不是 err == EOF。(成功读取完毕应该为 err == io.EOF,这里返回 nil 由于该函数成功期望 err == io.EOF,符合无错误不处理的理念)
ReadDir函数
ioutil 中提供了一个方便的函数:ReadDir,它读取目录并返回排好序的文件和子目录名
下面是一个读取目录并根据层级返回文件信息,提供一个类似于 tree 命令的效果展示
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 func main() {
// 这里传0,是为了在遍历目录下的文件时添加制表符,这样可以一眼看出这个文件是属于哪一个目录的
listAll("/home/yunzhou/note/study_go",0)
}
func listAll(path string, curHier int){
fileInfos, err := ioutil.ReadDir(path)
if err != nil{
fmt.Println(err);
return
}
for _, info := range fileInfos{
if info.IsDir(){
for tmpHier := curHier; tmpHier > 0; tmpHier--{
fmt.Printf("|\t")
}
fmt.Println(info.Name(),"\\")
listAll(path + "/" + info.Name(),curHier + 1)
}else{
for tmpHier := curHier; tmpHier > 0; tmpHier--{
fmt.Printf("|\t")
}
fmt.Println(info.Name())
}
}
}
// 输出
.idea \
| .gitignore
| modules.xml
| study_go.iml
| workspace.xml
01_standardLibrary \
| inout \
| | io.go
go.mod
main.go
ReadFile 和 WriteFile 函数
ReadFile 读取整个文件的内容,在上一节我们自己实现了一个函数读取文件整个内容,由于这种需求很常见,因此 Go 提供了 ReadFile 函数,方便使用。ReadFile 的实现和ReadAll 类似,不过,ReadFile 会先判断文件的大小,给 bytes.Buffer 一个预定义容量,避免额外分配内存。
ReadFile 函数的签名如下:
1 | |
ReadFile 从 filename 指定的文件中读取数据并返回文件的内容。成功的调用返回的err 为 nil 而非 EOF。因为本函数定义为读取整个文件,它不会将读取返回的 EOF 视为应报告的错误。(同 ReadAll )
WriteFile 函数的签名如下:
1 | |
WriteFile 将data写入filename文件中,当文件不存在时会根据perm指定的权限进行创建一个,文件存在时会先清空文件内容。对于 perm 参数,我们一般可以指定为:0666,具体含义 os 包中讲解。
fmt — 格式化
fmt 包实现了格式化I/O函数,类似于C的 printf 和 scanf. 格式“占位符”衍生自C,但比C更简单。
fmt 包的官方文档对 Printing 和 Scanning 有很详细的说明。这里就直接引用文档进行说明,同时附上额外的说明或例子,之后再介绍具体的函数使用。
fmt.Printf函数
1 | |