ioutil
是io
库的辅助工具函数库,用于实现I/O实用程序功能。
工具函数 | 返回值 | 描述 |
---|---|---|
ReadAll | []byte | 读取数据返回读取到的字节切片 |
ReadDir | []os.FileInfo | 读取目录返回目录入口数组 |
ReadFile | []byte | 读取文件返回文件内容的字节切片 |
WriteFile | error | 根据文件路径写入字节切片 |
TempDir | string | 在指定目录中创建指定前缀的临时文件夹,返回临时目录路径。 |
TempFile | os.File | 在指定目录创建指定前缀的临时文件 |
ioutil.ReadAll
ReadAll()
可用来一次性的读取数据
ReadAll()
会从读取器对象中读取数据直到遇到错误或EOF,返回读取的数据和错误,读取成功时返回的错误为nil
而非EOF。由于读取限制条件为读取直到EOF,因此不会将读取返回的EOF视为要报告的错误。
package ioutil
func ReadAll(r io.Reader) ([]byte, error) {
return io.ReadAll(r)
}
package io
func ReadAll(r Reader) ([]byte, error) {
b := make([]byte, 0, 512)
for {
if len(b) == cap(b) {
// Add more capacity (let append pick how much).
b = append(b, 0)[:len(b)]
}
n, err := r.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
if err != nil {
if err == EOF {
err = nil
}
return b, err
}
}
}
ioutil.ReadAll()
调用的是io.ReadAll()
,io.ReadAll()
默认会固定地申请512字节的缓存空间,同时将数据全部加载到内存。
比如:计算并获取文件的MD5值
问题:若一次性读取的文件大于2GB,由于util.ReadAll()
会将整个文件都加载到内存,短时间内是无法清理的。
//FileMd5 获取文件的MD5值
func FileMd5(filename string) string {
fp, err := os.Open(filename)
defer fp.Close()
buf, err := ioutil.ReadAll(fp)
if err != nil {
fmt.Printf("%+v\n", err)
return ""
}
md5Str := fmt.Sprintf("%x", md5.Sum(buf))
return md5Str
}
func main() {
file := "./README.md"
fileMd5 := FileMd5(file)
fmt.Printf("%s\n", fileMd5)
}
优化:此种情况最好采用io.Copy()
来替代ioutil.ReadAll()
//FileMd5 获取文件的MD5值
func FileMd5(filename string) (str string, err error) {
fp, err := os.Open(filename)
defer fp.Close()
if err != nil {
return
}
h := md5.New()
_, err = io.Copy(h, fp)
if err != nil {
return
}
str = fmt.Sprintf("%x", h.Sum(nil))
return
}
func main() {
file := "./README.md"
str, err := FileMd5(file)
fmt.Printf("%s\n", str)
fmt.Printf("%+v\n", err)
}
若数据过大会导致bytes.ErrTooLarge
异常。由于这512字节的缓存空间默认是固定申请的,即使读取的数据只有1字节也会申请512字节的缓存空间。因此在读取文件和网络请求时,存在性能隐患,可能会引发内存异常。
例如:从字符串中读取
func main() {
var str string
var buf []byte
var err error
var reader io.Reader
str = "hello world"
reader = strings.NewReader(str)
buf, err = ioutil.ReadAll(reader)
if err != nil {
fmt.Printf("%+v\n", err)
return
}
fmt.Printf("%s\n", buf)
}
ioutil.ReadDir
ioutil.ReadDir()
用于读取指定路径下所有的名录和文件,但不包含子目录。
func ReadDir(dirname string) ([]os.FileInfo, error)
返回读取到的经排序后的文件信息列表[]os.FileInfo
,os.FileInfo
接口提供了访问文件信息的方法。
type FileInfo interface {
Name() string // 文件基础名称
Size() int64 // 常规文件的字节长度
Mode() FileMode // 文件权限的比特位
ModTime() time.Time // 文件修改时间
IsDir() bool // 是否目录 Mode().IsDir()
Sys() interface{} // 基础数据源接口,可能为nil。
}
例如:获取当前目录文件
func main() {
fi, err := ioutil.ReadDir(".")
if err != nil {
log.Fatal(err)
}
for _, v := range fi {
fmt.Printf("%s\n", v.Name())
}
}
例如:递归获取指定路径下所有文件
func DirFile(pathname string, s []string) ([]string, error) {
dirname := filepath.FromSlash(pathname)
rd, err := ioutil.ReadDir(dirname)
if err != nil {
return s, err
}
for _, v := range rd {
if v.IsDir() {
s, err = DirFile(filepath.Join(dirname, v.Name()), s)
if err != nil {
return s, err
}
} else {
s = append(s, filepath.Join(dirname, v.Name()))
}
}
return s, nil
}
func main() {
s := []string{}
s, err := DirFile("d:/go/root", s)
fmt.Printf("%+v\n%+v\n%+v\n", err, s, len(s))
}
ioutil.ReadFile
ioutil.ReadFile()
用于读取文件中的所有数据,读取成功时,返回数据将以字节切片方式输出,错误值为nil
而非io.EOF
。
func ReadFile(filename string) ([]byte, error)
返回值 | 描述 |
---|---|
[]byte | 读取到的文件内容 |
error | 读取成功为nil ,读取失败为错误。 |
使用ioutil.ReadFile()
读取文件内容时,只需要一个文件名即可,无需手动打开和关闭文件。
filename := "./go.mod"
buf, err := ioutil.ReadFile(filename)
fmt.Printf("%+v\n%s\n", err, buf)
ioutil.ReadFile()
适用于读取小文件,不适合读取大文件。
ioutil.ReadFile()
读取文件时会先计算出文件的大小,再初始化对应大小的缓存后来读取字节流,相比之下速度更快。
ioutil.WriteFile
ioutil.WriteFile()
写文件前无需判断文件是否存在
- 若文件不存在会以指定权限自动创建后写入数据
- 若文件存在则会清空文件但不改变权限,然后覆盖原内容。
func WriteFile(filename string, data []byte, perm os.FileMode) error
参数 | 类型 | 描述 |
---|---|---|
filename | string | 文件路径 |
data | []byte | 要写入的文件内容 |
perm | os.FileMode | 文件权限 |
例如:写入文件后读取文件内容
filename := "./test.log"
data := []byte("hello world")
err := ioutil.WriteFile(filename, data, 0666)
fmt.Printf("%+v\n", err)
buf, err := ioutil.ReadFile(filename)
fmt.Printf("%+v\n%s\n", err, buf)
使用ioutil.WriteFile()
时若文件存在会清空后再写入,如何对存在文件进行内容追加呢?
func AppendFile(filename string, data []byte, perm os.FileMode) error {
f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE, perm)
defer f.Close()
if err != nil {
return err
}
n, err := f.Write(data)
if err != nil {
return err
}
if n < len(data) {
return io.ErrShortWrite
}
return nil
}
func main() {
filename := "./test.log"
data := []byte("\nhello world")
err := AppendFile(filename, data, 0644)
fmt.Println(err)
}
ioutil.WriteFile()
写文件时,如果目标文件已存在,则perm
属性会被忽略。
ioutil.TempFile
临时文件
临时文件是一个程序运行时才会创建,程序执行结束就无用的文件。因此不管创建的临时文件是否已经存在,程序都应该以读写的方式打开,一旦打开就会抹除原来的内容。由于程序结束时就变得无用,因此需要在程序结束时能够自动删除。
现代操作系统都提供了临时文件夹,临时文件夹表示重启操作系统后其下的内容可能会被删除的目录。
由于临时文件的创建和读写很频繁,因此大部分操作系统都提供了相关的API来创建和读写临时文件夹。大部分语言内置的标准库也提供了相关的方法或模块来创建和读写文件。
临时目录
现代操作系统都提供了一个或几个专用的文件夹用来保存临时文件,调用系统提供的临时文件操作函数会在旗下创建临时文件。
- Windows下临时目录由环境变量
%TMP%
、%TEMP%
、%USERPROFILE%
指定,默认临时目录位于C:\Users\[username]\AppData\Local\Temp\
。 - Linux/MacOS上临时目录由
$TMPDIR
环境变量指定,若无则默认位置为/tmp
。
Go标准库os
包提供了os.TempDir()
用于获取当前操作系统临时目录的路径
fmt.Printf("%+v\n", os.TempDir()) // C:\Users\z5j2c\AppData\Local\Temp
Go标准库io/ioutil
包也提供了创建临时目录和临时文件的函数
ioutil.TempFile()
用于创建临时文件,会在指定目录下创建指定前缀的临时文件,返回文件指针。若指定目录不存在则使用系统默认的临时目录。
func TempFile(dir, pattern string) (f *os.File, err error) {
return os.CreateTemp(dir, pattern)
}
参数 | 类型 | 描述 |
---|---|---|
dir | string | 用于指定临时文件保存的文件夹,若为空则会自动调用os.TempDir() 返回系统临时目录。 |
pattern | string | 用于指定临时文件的文件名格式 |
pattern
类似正则表达式的文件名格式,可使用*
表示随机字符串的位置,若无*
则自动会将随机字符串添加到文件名末尾。
返回值是一个os.File
类型的文件指针,可使用该类型提供的各种函数来读写文件。
注意:操作系统可能会自动删除临时文件,但并不一定会立即发生。所以临时文件使用完毕后最好手动调用os.Remove(file.Name()
来删除。
例如:
func main() {
//获取临时目录位置
fmt.Printf("%+v\n", os.TempDir()) // C:\Users\z5j2c\AppData\Local\Temp
//创建临时文件
f, err := ioutil.TempFile("", "cfg_")
defer f.Close()
fmt.Printf("%+v %+v\n", err, f.Name()) // <nil> C:\Users\z5j2c\AppData\Local\Temp\cfg_3128524677
//向临时文件写入字符串
f.WriteString("hello world")
//读取临时文件内容
buf, err := ioutil.ReadFile(f.Name())
fmt.Printf("%+v %s\n", err, buf) // <nil> hello world
//删除临时文件
defer os.Remove(f.Name())
}
ioutil.TempDir
ioutil.TempDir()
会在指定目录下创建一个全新的使用指定前缀的临时文件夹,若未指定目录则使用默认临时目录。
func TempDir(dir, pattern string) (name string, err error) {
return os.MkdirTemp(dir, pattern)
}
例如:在系统临时目录下随机创建临时目录
- 使用
os.TempDir()
获取系统临时目录路径
fmt.Printf("%+v\n", os.TempDir()) // C:\Users\z5j2c\AppData\Local\Temp
name, err := ioutil.TempDir("", "temp")
fmt.Printf("%+v %s\n", err, name) // <nil> C:\Users\z5j2c\AppData\Local\Temp\temp1983286483