ioutil

ioutilio库的辅助工具函数库,用于实现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.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
参数 类型 描述
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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • ioutil包提供给外部使用的一共有1个变量,7个方法。其中变量Discard是io.Write接口类型,调用该变...
    这题有点难阅读 5,375评论 0 1
  • 1.ioutil实现一些I / O实用方法。 import "io/ioutil" var Discard io....
    第八共同体阅读 4,263评论 0 3
  • // Discard 是一个 io.Writer 接口,调用它的 Write 方法将不做任何事情// 并且始终成功...
    佛心看世界阅读 782评论 0 1
  • 读取 ReadAll 读取全部数据 func ReadAll(r io.Reader) ([]byte, erro...
    copyLeft阅读 926评论 0 0
  • 概述 ioutil包实现了一些I/O使用函数 ReadAll 原型 对r进行读取,直到发生错误或者遇到EOF,所以...
    killtl阅读 807评论 0 0