ioutil.go
2019-03-26 本文已影响0人
killtl
概述
ioutil包实现了一些I/O使用函数
ReadAll
原型
func ReadAll(r io.Reader) ([]byte, error) {
return readAll(r, bytes.MinRead)
}
对r
进行读取,直到发生错误或者遇到EOF
,所以一次成功的读取将返回nil
而不是EOF
作为err
的值
示例
package main
import (
"strings"
"io/ioutil"
"log"
"fmt"
)
func main() {
r := strings.NewReader("joker")
b, err := ioutil.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
}
// output
joker
源码
ReadAll
方法实际调用的是ioutil包的内部方法readAll
func readAll(r io.Reader, capacity int64) (b []byte, err error) {
var buf bytes.Buffer
// 如果buffer缓冲区溢出,会返回bytes.ErrTooLarge类型的error
// 否则直接panic中断
defer func() {
e := recover()
if e == nil {
return
}
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
err = panicErr
} else {
panic(e)
}
}()
// 这里判断int64类型的capacity数值是否在int类型可表示的范围内,如果在的话,一次性通过
// buffer.Grow进行内存扩从,避免后期读取的时候频繁扩展,降低性能消耗
if int64(int(capacity)) == capacity {
buf.Grow(int(capacity))
}
_, err = buf.ReadFrom(r)
return buf.Bytes(), err
}
ReadDir
原型
func ReadDir(dirname string) ([]os.FileInfo, error)
读取dirname
指定的目录,并返回一个根据文件名进行排序的目录节点列表
示例
package main
import (
"io/ioutil"
"log"
"fmt"
)
func main() {
files, err := ioutil.ReadDir(".")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
fmt.Println(file.Name())
}
}
源码
func ReadDir(dirname string) ([]os.FileInfo, error) {
f, err := os.Open(dirname)
if err != nil {
return nil, err
}
// -1 读取所有文件文件信息
list, err := f.Readdir(-1)
// 这里不使用defer,手动进行Close释放
// 避免文件数目过多,后面的排序时间相对略长导致资源占用
f.Close()
if err != nil {
return nil, err
}
// 根据文件名排序
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
return list, nil
}
ReadFile
原型
func ReadFile(filename string) ([]byte, error)
读取filename
文件并返回文件中的内容,由于是读取整个文件,所以返回的err不会是EOF
示例
package main
import (
"io/ioutil"
"log"
"fmt"
)
func main() {
file, err := ioutil.ReadFile("glide.lock")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(file))
}
源码
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
// 文件读取缓冲区默认初始化大小
var n int64 = bytes.MinRead
if fi, err := f.Stat(); err == nil {
// 根据文件实际大小初始化缓冲区大小,避免运行时重复内存分配
// 这里可以看到ReadFile和ReadAll的区别,ReadAll会使用默认的capacity,读取大文件ReadFile比ReadAll性能更好
if size := fi.Size() + bytes.MinRead; size > n {
n = size
}
}
return readAll(f, n)
}
WriteFile
原型
func WriteFile(filename string, data []byte, perm os.FileMode) error
将给定的数据data
写入到filename
文件中,如果filename
不存在,使用给定的权限perm去创建,如果filename
已经存在,则在写入之前清空文件中已有的内容
示例
package main
import (
"io/ioutil"
"os"
"log"
"fmt"
)
func main() {
err := ioutil.WriteFile("test", []byte("joker"), os.ModePerm)
if err != nil {
log.Fatal(err)
}
file, err := ioutil.ReadFile("test")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(file))
}
源码
func WriteFile(filename string, data []byte, perm os.FileMode) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
n, err := f.Write(data)
// data是否全部写入成功
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
// 这里需要关注下写入Close()有可能报错,不能跟读取一样放到defer里面
// 因为Close的错误有可能导致写入的内容没有落地到硬盘
// 具体原因可以参见这里 https://www.joeshaw.org/dont-defer-close-on-writable-files/
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
总结
不得不说,go包的源码考虑十分周到,舒适啊~