ioutil

ioutilio库的辅助工具函数库,用于实现I/O实用程序功能。

工具函数返回值描述
ReadAll[]byte读取数据返回读取到的字节切片
ReadDir[]os.FileInfo读取目录返回目录入口数组
ReadFile[]byte读取文件返回文件内容的字节切片
WriteFileerror根据文件路径写入字节切片
TempDirstring在指定目录中创建指定前缀的临时文件夹,返回临时目录路径。
TempFileos.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.FileInfoos.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
参数类型描述
filenamestring文件路径
data[]byte要写入的文件内容
permos.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)
}
参数类型描述
dirstring用于指定临时文件保存的文件夹,若为空则会自动调用os.TempDir()返回系统临时目录。
patternstring用于指定临时文件的文件名格式

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
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值