ioutil是io庫(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