ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 1.2 ioutil — 方便的IO操作函数集 # 虽然 io 包提供了不少类型、方法和函数,但有时候使用起来不是那么方便。比如读取一个文件中的所有内容。为此,标准库中提供了一些常用、方便的IO操作函数。 说明:这些函数使用都相对简单,一般就不举例子了。 ## NopCloser 函数 ## 有时候我们需要传递一个io.ReadCloser的实例,而我们现在有一个io.Reader的实例,比如:strings.Reader,这个时候NopCloser就派上用场了。它包装一个io.Reader,返回一个io.ReadCloser,而相应的Close方法啥也不做,只是返回nil。 比如,在标准库net/http包中的NewRequest,接收一个io.Reader的body,而实际上,Request的Body的类型是io.ReadCloser,因此,代码内部进行了判断,如果传递的io.Reader也实现了io.ReadCloser接口,则转换,否则通过ioutil.NopCloser包装转换一下。相关代码如下: rc, ok := body.(io.ReadCloser) if !ok && body != nil { rc = ioutil.NopCloser(body) } 如果没有这个函数,我们得自己实现一个。当然,实现起来很简单,读者可以看看NopCloser的实现。 ## ReadAll 函数 ## 很多时候,我们需要一次性读取io.Reader中的数据,通过上一节的讲解,我们知道有很多种实现方式。考虑到读取所有数据的需求比较多,Go提供了ReadAll这个函数,用来从io.Reader中一次读取所有数据。 func ReadAll(r io.Reader) ([]byte, error) 阅读该函数的源码发现,它是通过bytes.Buffer中的ReadFrom来实现读取所有数据的。 ## ReadDir 函数 ## 笔试题:编写程序输出某目录下的所有文件(包括子目录) 是否见过这样的笔试题? 在Go中如何输出目录下的所有文件呢?首先,我们会想到查os包,看File类型是否提供了相关方法(关于os包,后面会讲解)。 其实在ioutil中提供了一个方便的函数:ReadDir,它读取目录并返回排好序的文件和子目录名([]os.FileInfo)。通过这个方法,我们可以很容易的实现“面试题”。 下面的例子实现了类似Unix中的tree命令,不过它在windows下也运行的很好哦。 // 未实现-L参数功能 func main() { if len(os.Args) > 1 { Tree(os.Args[1], 1, map[int]bool{1:true}) } } // 列出dirname目录中的目录树,实现类似Unix中的tree命令 // curHier 当前层级(dirname为第一层) // hierMap 当前层级的上几层是否需要'|'的映射 func Tree(dirname string, curHier int, hierMap map[int]bool) error { dirAbs, err := filepath.Abs(dirname) if err != nil { return err } fileInfos, err := ioutil.ReadDir(dirAbs) if err != nil { return err } fileNum := len(fileInfos) for i, fileInfo := range fileInfos { for j := 1; j < curHier; j++ { if hierMap[j] { fmt.Print("|") } else { fmt.Print(" ") } fmt.Print(strings.Repeat(" ", 3)) } // map是引用类型,所以新建一个map tmpMap := map[int]bool{} for k, v := range hierMap { tmpMap[k] = v } if i+1 == fileNum { fmt.Print("`") delete(tmpMap, curHier) } else { fmt.Print("|") tmpMap[curHier] = true } fmt.Print("-- ") fmt.Println(fileInfo.Name()) if fileInfo.IsDir() { Tree(filepath.Join(dirAbs, fileInfo.Name()), curHier+1, tmpMap) } } return nil } ## ReadFile 和 WriteFile 函数 ## ReadFile 读取整个文件的内容,在上一节我们自己实现了一个函数读取文件整个内容,由于这种需求很常见,因此Go提供了ReadFile函数,方便使用。ReadFile的是实现和ReadAll类似,不过,ReadFile会先判断文件的大小,给bytes.Buffer一个预定义容量,避免额外分配内存。 WriteFile 函数的签名如下: func WriteFile(filename string, data []byte, perm os.FileMode) error 它将data写入filename文件中,当文件不存在时会创建一个(文件权限由perm指定);否则会先清空文件内容。对于perm参数,我们一般可以指定为:0666,具体含义os包中讲解。 **小提示** ReadFile 源码中先获取了文件的大小,当大小 < 1e9时,才会用到文件的大小。按源码中注释的说法是FileInfo不会很精确地得到文件大小。 ## TempDir 和 TempFile 函数 ## 操作系统中一般都会提供临时目录,比如linux下的/tmp目录(通过os.TempDir()可以获取到)。有时候,我们自己需要创建临时目录,比如Go工具链源码中(src/cmd/go/build.go),通过TempDir创建一个临时目录,用于存放编译过程的临时文件: b.work, err = ioutil.TempDir("", "go-build") 第一个参数如果为空,表明在系统默认的临时目录(os.TempDir)中创建临时目录;第二个参数指定临时目录名的前缀,该函数返回临时目录的路径。 相应的,TempFile用于创建临时文件。如gofmt命令的源码中创建临时文件: f1, err := ioutil.TempFile("", "gofmt") 参数和ioutil.TempDir参数含义类似。 这里需要**注意**:创建者创建的临时文件和临时目录要负责删除这些临时目录和文件。如删除临时文件: defer func() { f.Close() os.Remove(f.Name()) }() ## Discard 变量 ## Discard 对应的类型(`type devNull int`)实现了io.Writer接口,同时,为了优化io.Copy到Discard,避免不必要的工作,实现了io.ReaderFrom接口。 devNull 在实现io.Writer接口时,只是简单的返回(标准库文件:src/pkg/io/ioutil.go)。 func (devNull) Write(p []byte) (int, error) { return len(p), nil } 而ReadFrom的实现是读取内容到一个buf中,最大也就8192字节,其他的会丢弃(当然,这个也不会读取)。 # 导航 # - [目录](/preface.md) - 上一节:[io — 基本的IO接口](01.1.md) - 下一节:[fmt — 格式化IO](01.3.md)