ioutil

ioutilio庫(kù)的輔助工具函數(shù)庫(kù),用于實(shí)現(xiàn)I/O實(shí)用程序功能。

工具函數(shù) 返回值 描述
ReadAll []byte 讀取數(shù)據(jù)返回讀取到的字節(jié)切片
ReadDir []os.FileInfo 讀取目錄返回目錄入口數(shù)組
ReadFile []byte 讀取文件返回文件內(nèi)容的字節(jié)切片
WriteFile error 根據(jù)文件路徑寫(xiě)入字節(jié)切片
TempDir string 在指定目錄中創(chuàng)建指定前綴的臨時(shí)文件夾,返回臨時(shí)目錄路徑。
TempFile os.File 在指定目錄創(chuàng)建指定前綴的臨時(shí)文件

ioutil.ReadAll

ReadAll()可用來(lái)一次性的讀取數(shù)據(jù)

ReadAll()會(huì)從讀取器對(duì)象中讀取數(shù)據(jù)直到遇到錯(cuò)誤或EOF,返回讀取的數(shù)據(jù)和錯(cuò)誤,讀取成功時(shí)返回的錯(cuò)誤為nil而非EOF。由于讀取限制條件為讀取直到EOF,因此不會(huì)將讀取返回的EOF視為要報(bào)告的錯(cuò)誤。

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()調(diào)用的是io.ReadAll()io.ReadAll()默認(rèn)會(huì)固定地申請(qǐng)512字節(jié)的緩存空間,同時(shí)將數(shù)據(jù)全部加載到內(nèi)存。

比如:計(jì)算并獲取文件的MD5值

問(wèn)題:若一次性讀取的文件大于2GB,由于util.ReadAll()會(huì)將整個(gè)文件都加載到內(nèi)存,短時(shí)間內(nèi)是無(wú)法清理的。

//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)
}

優(yōu)化:此種情況最好采用io.Copy()來(lái)替代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)
}

若數(shù)據(jù)過(guò)大會(huì)導(dǎo)致bytes.ErrTooLarge異常。由于這512字節(jié)的緩存空間默認(rèn)是固定申請(qǐng)的,即使讀取的數(shù)據(jù)只有1字節(jié)也會(huì)申請(qǐng)512字節(jié)的緩存空間。因此在讀取文件和網(wǎng)絡(luò)請(qǐng)求時(shí),存在性能隱患,可能會(huì)引發(fā)內(nèi)存異常。

例如:從字符串中讀取

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)

返回讀取到的經(jīng)排序后的文件信息列表[]os.FileInfo,os.FileInfo接口提供了訪問(wèn)文件信息的方法。

type FileInfo interface {
    Name() string       // 文件基礎(chǔ)名稱(chēng)
    Size() int64        // 常規(guī)文件的字節(jié)長(zhǎng)度
    Mode() FileMode     // 文件權(quán)限的比特位
    ModTime() time.Time // 文件修改時(shí)間
    IsDir() bool        // 是否目錄 Mode().IsDir()
    Sys() interface{}   // 基礎(chǔ)數(shù)據(jù)源接口,可能為nil。
}

例如:獲取當(dāng)前目錄文件

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()用于讀取文件中的所有數(shù)據(jù),讀取成功時(shí),返回?cái)?shù)據(jù)將以字節(jié)切片方式輸出,錯(cuò)誤值為nil而非io.EOF。

func ReadFile(filename string) ([]byte, error)
返回值 描述
[]byte 讀取到的文件內(nèi)容
error 讀取成功為nil,讀取失敗為錯(cuò)誤。

使用ioutil.ReadFile()讀取文件內(nèi)容時(shí),只需要一個(gè)文件名即可,無(wú)需手動(dòng)打開(kāi)和關(guān)閉文件。

filename := "./go.mod"
buf, err := ioutil.ReadFile(filename)
fmt.Printf("%+v\n%s\n", err, buf)

ioutil.ReadFile()適用于讀取小文件,不適合讀取大文件。

ioutil.ReadFile()讀取文件時(shí)會(huì)先計(jì)算出文件的大小,再初始化對(duì)應(yīng)大小的緩存后來(lái)讀取字節(jié)流,相比之下速度更快。

ioutil.WriteFile

ioutil.WriteFile()寫(xiě)文件前無(wú)需判斷文件是否存在

  • 若文件不存在會(huì)以指定權(quán)限自動(dòng)創(chuàng)建后寫(xiě)入數(shù)據(jù)
  • 若文件存在則會(huì)清空文件但不改變權(quán)限,然后覆蓋原內(nèi)容。
func WriteFile(filename string, data []byte, perm os.FileMode) error
參數(shù) 類(lèi)型 描述
filename string 文件路徑
data []byte 要寫(xiě)入的文件內(nèi)容
perm os.FileMode 文件權(quán)限

例如:寫(xiě)入文件后讀取文件內(nèi)容

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()時(shí)若文件存在會(huì)清空后再寫(xiě)入,如何對(duì)存在文件進(jìn)行內(nèi)容追加呢?

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()寫(xiě)文件時(shí),如果目標(biāo)文件已存在,則perm屬性會(huì)被忽略。

ioutil.TempFile

臨時(shí)文件

臨時(shí)文件是一個(gè)程序運(yùn)行時(shí)才會(huì)創(chuàng)建,程序執(zhí)行結(jié)束就無(wú)用的文件。因此不管創(chuàng)建的臨時(shí)文件是否已經(jīng)存在,程序都應(yīng)該以讀寫(xiě)的方式打開(kāi),一旦打開(kāi)就會(huì)抹除原來(lái)的內(nèi)容。由于程序結(jié)束時(shí)就變得無(wú)用,因此需要在程序結(jié)束時(shí)能夠自動(dòng)刪除。

現(xiàn)代操作系統(tǒng)都提供了臨時(shí)文件夾,臨時(shí)文件夾表示重啟操作系統(tǒng)后其下的內(nèi)容可能會(huì)被刪除的目錄。

由于臨時(shí)文件的創(chuàng)建和讀寫(xiě)很頻繁,因此大部分操作系統(tǒng)都提供了相關(guān)的API來(lái)創(chuàng)建和讀寫(xiě)臨時(shí)文件夾。大部分語(yǔ)言內(nèi)置的標(biāo)準(zhǔn)庫(kù)也提供了相關(guān)的方法或模塊來(lái)創(chuàng)建和讀寫(xiě)文件。

臨時(shí)目錄

現(xiàn)代操作系統(tǒng)都提供了一個(gè)或幾個(gè)專(zhuān)用的文件夾用來(lái)保存臨時(shí)文件,調(diào)用系統(tǒng)提供的臨時(shí)文件操作函數(shù)會(huì)在旗下創(chuàng)建臨時(shí)文件。

  • Windows下臨時(shí)目錄由環(huán)境變量%TMP%、%TEMP%%USERPROFILE%指定,默認(rèn)臨時(shí)目錄位于C:\Users\[username]\AppData\Local\Temp\
  • Linux/MacOS上臨時(shí)目錄由$TMPDIR環(huán)境變量指定,若無(wú)則默認(rèn)位置為/tmp

Go標(biāo)準(zhǔn)庫(kù)os包提供了os.TempDir()用于獲取當(dāng)前操作系統(tǒng)臨時(shí)目錄的路徑

fmt.Printf("%+v\n", os.TempDir()) // C:\Users\z5j2c\AppData\Local\Temp

Go標(biāo)準(zhǔn)庫(kù)io/ioutil包也提供了創(chuàng)建臨時(shí)目錄和臨時(shí)文件的函數(shù)

ioutil.TempFile()用于創(chuàng)建臨時(shí)文件,會(huì)在指定目錄下創(chuàng)建指定前綴的臨時(shí)文件,返回文件指針。若指定目錄不存在則使用系統(tǒng)默認(rèn)的臨時(shí)目錄。

func TempFile(dir, pattern string) (f *os.File, err error) {
    return os.CreateTemp(dir, pattern)
}
參數(shù) 類(lèi)型 描述
dir string 用于指定臨時(shí)文件保存的文件夾,若為空則會(huì)自動(dòng)調(diào)用os.TempDir()返回系統(tǒng)臨時(shí)目錄。
pattern string 用于指定臨時(shí)文件的文件名格式

pattern類(lèi)似正則表達(dá)式的文件名格式,可使用*表示隨機(jī)字符串的位置,若無(wú)*則自動(dòng)會(huì)將隨機(jī)字符串添加到文件名末尾。

返回值是一個(gè)os.File類(lèi)型的文件指針,可使用該類(lèi)型提供的各種函數(shù)來(lái)讀寫(xiě)文件。

注意:操作系統(tǒng)可能會(huì)自動(dòng)刪除臨時(shí)文件,但并不一定會(huì)立即發(fā)生。所以臨時(shí)文件使用完畢后最好手動(dòng)調(diào)用os.Remove(file.Name()來(lái)刪除。

例如:

func main() {
    //獲取臨時(shí)目錄位置
    fmt.Printf("%+v\n", os.TempDir()) // C:\Users\z5j2c\AppData\Local\Temp

    //創(chuàng)建臨時(shí)文件
    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

    //向臨時(shí)文件寫(xiě)入字符串
    f.WriteString("hello world")

    //讀取臨時(shí)文件內(nèi)容
    buf, err := ioutil.ReadFile(f.Name())
    fmt.Printf("%+v %s\n", err, buf) // <nil> hello world

    //刪除臨時(shí)文件
    defer os.Remove(f.Name())
}

ioutil.TempDir

ioutil.TempDir()會(huì)在指定目錄下創(chuàng)建一個(gè)全新的使用指定前綴的臨時(shí)文件夾,若未指定目錄則使用默認(rèn)臨時(shí)目錄。

func TempDir(dir, pattern string) (name string, err error) {
    return os.MkdirTemp(dir, pattern)
}

例如:在系統(tǒng)臨時(shí)目錄下隨機(jī)創(chuàng)建臨時(shí)目錄

  • 使用os.TempDir()獲取系統(tǒng)臨時(shí)目錄路徑
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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ioutil包提供給外部使用的一共有1個(gè)變量,7個(gè)方法。其中變量Discard是io.Write接口類(lèi)型,調(diào)用該變...
    ljh123閱讀 5,492評(píng)論 0 1
  • 1.ioutil實(shí)現(xiàn)一些I / O實(shí)用方法。 import "io/ioutil" var Discard io....
    第八共同體閱讀 4,419評(píng)論 0 3
  • // Discard 是一個(gè) io.Writer 接口,調(diào)用它的 Write 方法將不做任何事情// 并且始終成功...
    佛心看世界閱讀 911評(píng)論 0 1
  • 讀取 ReadAll 讀取全部數(shù)據(jù) func ReadAll(r io.Reader) ([]byte, erro...
    copyLeft閱讀 998評(píng)論 0 0
  • 概述 ioutil包實(shí)現(xiàn)了一些I/O使用函數(shù) ReadAll 原型 對(duì)r進(jìn)行讀取,直到發(fā)生錯(cuò)誤或者遇到EOF,所以...
    killtl閱讀 881評(píng)論 0 0

友情鏈接更多精彩內(nèi)容