一乌助、API
參考Go語言學(xué)習(xí)筆記(五)文件操作
1.os.File
type File
File代表一個打開的文件對象嘴纺。func Create(name string) (file *File, err error)
Create采用模式0666(任何人都可讀寫,不可執(zhí)行)創(chuàng)建一個名為name的文件如输,如果文件已存在會截斷它(為空文件)疗隶。如果成功,返回的文件對象可用于I/O寄狼;對應(yīng)的文件描述符具有O_RDWR模式。如果出錯氨淌,錯誤底層類型是*PathError泊愧。func Open(name string) (file *File, err error)
Open打開一個文件用于讀取。如果操作成功盛正,返回的文件對象的方法可用于讀取數(shù)據(jù)删咱;對應(yīng)的文件描述符具有O_RDONLY模式。如果出錯豪筝,錯誤底層類型是*PathError痰滋。func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
OpenFile是一個更一般性的文件打開函數(shù),大多數(shù)調(diào)用者都應(yīng)用Open或Create代替本函數(shù)续崖。它會使用指定的選項(如O_RDONLY等)敲街、指定的模式(如0666等)打開指定名稱的文件。如果操作成功严望,返回的文件對象可用于I/O多艇。如果出錯,錯誤底層類型是*PathError像吻。func NewFile(fd uintptr, name string) *File
NewFile使用給出的Unix文件描述符和名稱創(chuàng)建一個文件峻黍。func Pipe() (r *File, w *File, err error)
Pipe返回一對關(guān)聯(lián)的文件對象复隆。從r的讀取將返回寫入w的數(shù)據(jù)。本函數(shù)會返回兩個文件對象和可能的錯誤姆涩。func (f *File) Name() string
Name方法返回(提供給Open/Create等方法的)文件名稱挽拂。func (f *File) Stat() (fi FileInfo, err error)
Stat返回描述文件f的FileInfo類型值。如果出錯骨饿,錯誤底層類型是*PathError亏栈。func (f *File) Fd() uintptr
Fd返回與文件f對應(yīng)的整數(shù)類型的Unix文件描述符。func (f *File) Chdir() error
Chdir將當(dāng)前工作目錄修改為f样刷,f必須是一個目錄。如果出錯览爵,錯誤底層類型是*PathError置鼻。func (f *File) Chmod(mode FileMode) error
Chmod修改文件的模式。如果出錯蜓竹,錯誤底層類型是*PathError箕母。func (f *File) Chown(uid, gid int) error
Chown修改文件的用戶ID和組ID。如果出錯俱济,錯誤底層類型是*PathError嘶是。func (f *File) Readdir(n int) (fi []FileInfo, err error)
Readdir讀取目錄f的內(nèi)容,返回一個有n個成員的[]FileInfo蛛碌,這些FileInfo是被Lstat返回的聂喇,采用目錄順序。對本函數(shù)的下一次調(diào)用會返回上一次調(diào)用剩余未讀取的內(nèi)容的信息蔚携。
如果n>0希太,Readdir函數(shù)會返回一個最多n個成員的切片。這時酝蜒,如果Readdir返回一個空切片誊辉,它會返回一個非nil的錯誤說明原因。如果到達(dá)了目錄f的結(jié)尾亡脑,返回值err會是io.EOF。
如果n<=0,Readdir函數(shù)返回目錄中剩余所有文件對象的FileInfo構(gòu)成的切片涩搓。此時拇派,如果Readdir調(diào)用成功(讀取所有內(nèi)容直到結(jié)尾),它會返回該切片和nil的錯誤值途戒。如果在到達(dá)結(jié)尾前遇到錯誤惊来,會返回之前成功讀取的FileInfo構(gòu)成的切片和該錯誤。func (f *File) Readdirnames(n int) (names []string, err error)
Readdir讀取目錄f的內(nèi)容棺滞,返回一個有n個成員的[]string裁蚁,切片成員為目錄中文件對象的名字矢渊,采用目錄順序。對本函數(shù)的下一次調(diào)用會返回上一次調(diào)用剩余未讀取的內(nèi)容的信息枉证。
如果n>0矮男,Readdir函數(shù)會返回一個最多n個成員的切片。這時室谚,如果Readdir返回一個空切片毡鉴,它會返回一個非nil的錯誤說明原因。如果到達(dá)了目錄f的結(jié)尾秒赤,返回值err會是io.EOF猪瞬。
如果n<=0,Readdir函數(shù)返回目錄中剩余所有文件對象的名字構(gòu)成的切片入篮。此時陈瘦,如果Readdir調(diào)用成功(讀取所有內(nèi)容直到結(jié)尾),它會返回該切片和nil的錯誤值潮售。如果在到達(dá)結(jié)尾前遇到錯誤痊项,會返回之前成功讀取的名字構(gòu)成的切片和該錯誤。func (f *File) Truncate(size int64) error
Truncate改變文件的大小酥诽,它不會改變I/O的當(dāng)前位置鞍泉。 如果截斷文件,多出的部分就會被丟棄肮帐。如果出錯咖驮,錯誤底層類型是*PathError。func (f *File) Read(b []byte) (n int, err error)
Read方法從f中讀取最多l(xiāng)en(b)字節(jié)數(shù)據(jù)并寫入b训枢。它返回讀取的字節(jié)數(shù)和可能遇到的任何錯誤游沿。文件終止標(biāo)志是讀取0個字節(jié)且返回值err為io.EOF。func (f *File) ReadAt(b []byte, off int64) (n int, err error)
ReadAt從指定的位置(相對于文件開始位置)讀取len(b)字節(jié)數(shù)據(jù)并寫入b肮砾。它返回讀取的字節(jié)數(shù)和可能遇到的任何錯誤诀黍。當(dāng)n<len(b)時,本方法總是會返回錯誤仗处;如果是因為到達(dá)文件結(jié)尾眯勾,返回值err會是io.EOF。func (f *File) Write(b []byte) (n int, err error)
Write向文件中寫入len(b)字節(jié)數(shù)據(jù)婆誓。它返回寫入的字節(jié)數(shù)和可能遇到的任何錯誤吃环。如果返回值n!=len(b),本方法會返回一個非nil的錯誤洋幻。func (f *File) WriteString(s string) (ret int, err error)
WriteString類似Write郁轻,但接受一個字符串參數(shù)。func (f *File) WriteAt(b []byte, off int64) (n int, err error)
WriteAt在指定的位置(相對于文件開始位置)寫入len(b)字節(jié)數(shù)據(jù)。它返回寫入的字節(jié)數(shù)和可能遇到的任何錯誤好唯。如果返回值n!=len(b)竭沫,本方法會返回一個非nil的錯誤。func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek設(shè)置下一次讀/寫的位置骑篙。offset為相對偏移量蜕提,而whence決定相對位置:0為相對文件開頭,1為相對當(dāng)前位置靶端,2為相對文件結(jié)尾谎势。它返回新的偏移量(相對開頭)和可能的錯誤。func (f *File) Sync() (err error)
Sync遞交文件的當(dāng)前內(nèi)容進行穩(wěn)定的存儲杨名。一般來說脏榆,這表示將文件系統(tǒng)的最近寫入的數(shù)據(jù)在內(nèi)存中的拷貝刷新到硬盤中穩(wěn)定保存。func (f *File) Close() error
Close關(guān)閉文件f台谍,使文件不能用于讀寫须喂。它返回可能出現(xiàn)的錯誤。
2.文件打開模式:
const (
O_RDONLY int = syscall.O_RDONLY // 只讀模式打開文件
O_WRONLY int = syscall.O_WRONLY // 只寫模式打開文件
O_RDWR int = syscall.O_RDWR // 讀寫模式打開文件
O_APPEND int = syscall.O_APPEND // 寫操作時將數(shù)據(jù)附加到文件尾部
O_CREATE int = syscall.O_CREAT // 如果不存在將創(chuàng)建一個新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用典唇,文件必須不存在
O_SYNC int = syscall.O_SYNC // 打開文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能镊折,打開時清空文件
)
二胯府、讀介衔,創(chuàng)建
logFile,err:=os.Open("log/system.txt")
if err!=nil{
log.Fatalln("讀取日志文件失敗",err)
}
defer logFile.Close()
logger:=log.New(logFile,"\r\n",log.Ldate|log.Ltime)
logger.Print("hello")
發(fā)現(xiàn)怎么都不能往system.txt文件中寫入hello字符串,改了一下:
logFile,err:=os.OpenFile("log/system.txt",os.O_RDWR|os.O_CREATE,0)
...
結(jié)論就是Open方法只能讀
2.創(chuàng)建
f,err := os.Create(fileName)
defer f.Close()
if err !=nil {
fmt.Println(err.Error())
} else {
_,err=f.Write([]byte("要寫入的文本內(nèi)容"))
checkErr(err)
}
三骂因、創(chuàng)建文件夾Mkdir
// 判斷文件夾是否存在
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func main() {
_dir := "./gzFiles2"
exist, err := PathExists(_dir)
if err != nil {
fmt.Printf("get dir error![%v]\n", err)
return
}
if exist {
fmt.Printf("has dir![%v]\n", _dir)
} else {
fmt.Printf("no dir![%v]\n", _dir)
// 創(chuàng)建文件夾
err := os.Mkdir(_dir, os.ModePerm)
if err != nil {
fmt.Printf("mkdir failed![%v]\n", err)
} else {
fmt.Printf("mkdir success!\n")
}
}
}
四炎咖、刪除
文件刪除的時候,不管是普通文件還是目錄文件寒波,都可以用err:=os.Remove(filename)這樣的操作來執(zhí)行乘盼。當(dāng)然要是想移除整個文件夾,直接使用RemoveAll(path string)操作即可俄烁〕裾ぃ可以看一下RemoveAll函數(shù)的內(nèi)部實現(xiàn),整體上就是遍歷页屠,遞歸的操作過程粹胯,其他的類似的文件操作都可以用類似的模板來實現(xiàn)
os.RemoveAll("./gzFiles2")
五、讀文件
這一部分較多的涉及I/O的相關(guān)操作辰企,系統(tǒng)的介紹放在I/O那部分來整理风纠,大體上向文件中讀寫內(nèi)容的時候有三種方式:
1、在使用f, err := os.Open(file_path)
打開文件之后直接使用 f.read() f.write()
結(jié)合自定義的buffer每次從文件中讀入/讀出固定的內(nèi)容
2牢贸、使用ioutl的readFile和writeFile方法
3竹观、使用bufio采用帶有緩存的方式進行讀寫,比如通過info:=bufio.NewReader(f)
將實現(xiàn)了io.Reader的接口的實例加載上來之后,就可以使用info.ReadLine()來每次實現(xiàn)一整行的讀取臭增,直到err信息為io.EOF時懂酱,讀取結(jié)束
Golang幾種讀文件方式的比較對三種文件操作的讀入速度進行了比較
package main
import(
"fmt"
"os"
"flag"
"io"
"io/ioutil"
"bufio"
"time"
)
func read1(path string)string{
fi,err := os.Open(path)
if err != nil{
panic(err)
}
defer fi.Close()
chunks := make([]byte,1024,1024)
buf := make([]byte,1024)
for{
n,err := fi.Read(buf)
if err != nil && err != io.EOF{panic(err)}
if 0 ==n {break}
chunks=append(chunks,buf[:n]...)
// fmt.Println(string(buf[:n]))
}
return string(chunks)
}
func read2(path string)string{
fi,err := os.Open(path)
if err != nil{panic(err)}
defer fi.Close()
r := bufio.NewReader(fi)
chunks := make([]byte,1024,1024)
buf := make([]byte,1024)
for{
n,err := r.Read(buf)
if err != nil && err != io.EOF{panic(err)}
if 0 ==n {break}
chunks=append(chunks,buf[:n]...)
// fmt.Println(string(buf[:n]))
}
return string(chunks)
}
func read3(path string)string{
fi,err := os.Open(path)
if err != nil{panic(err)}
defer fi.Close()
fd,err := ioutil.ReadAll(fi)
// fmt.Println(string(fd))
return string(fd)
}
func main(){
flag.Parse()
file := flag.Arg(0)
f,err := ioutil.ReadFile(file)
if err != nil{
fmt.Printf("%s\n",err)
panic(err)
}
fmt.Println(string(f))
start := time.Now()
read1(file)
t1 := time.Now()
fmt.Printf("Cost time %v\n",t1.Sub(start))
read2(file)
t2 := time.Now()
fmt.Printf("Cost time %v\n",t2.Sub(t1))
read3(file)
t3 := time.Now()
fmt.Printf("Cost time %v\n",t3.Sub(t2))
}
運行命令go run read.go filename, 指定需要讀取的文件就可以測試了。
在golang bufio速址、ioutil讀文件的速度比較(性能測試)和影響因素分析 中玩焰,給出了結(jié)論:
在查閱golang標(biāo)準(zhǔn)庫的源代碼后,之所以有不同的結(jié)果是與每個方法的實現(xiàn)相關(guān)的芍锚,最大的因素就是內(nèi)部buffer的大小昔园,這個直接決定了讀取的快慢:
- 1.f.Read()底層實現(xiàn)是系統(tǒng)調(diào)用syscall.Read(),沒有深究
- 2.bufio.NewReader(f)實際調(diào)用NewReaderSize(f, defaultBufSize)并炮,而defaultBufSize=4096默刚,可以直接用bufio.NewReaderSize(f,32768)來預(yù)分配更大的緩存,緩存的實質(zhì)是make([]byte, size)
- 3.ioutil.ReadAll(f)實際調(diào)用readAll(r, bytes.MinRead)逃魄,而bytes.MinRead=512荤西,緩存的實質(zhì)是bytes.NewBuffer(make([]byte, 0, 512),雖然bytes.Buffer會根據(jù)情況自動增大伍俘,但每次重新分配都會影響性能
- 4.ioutil.ReadFile(path)是調(diào)用readAll(f, n+bytes.MinRead)邪锌,這個n取決于文件大小,文件小于10^9字節(jié)(0.93GB)癌瘾,n=文件大小觅丰,就是NewBuffer一個略大于文件大小的緩存,非撤镣耍慷慨妇萄;大于則n=0,好慘咬荷,也就是說大于1G的文件就跟ioutil.ReadAll(f)一個樣子了冠句。
- 5.但全量緩存的ReadFile為什么不如大塊讀取的前兩者呢?我猜測是NewBuffer包裝的字節(jié)數(shù)組性能當(dāng)然不如裸奔的字符數(shù)組幸乒。懦底。
結(jié)論
- ?當(dāng)每次讀取塊的大小小于4KB,建議使用bufio.NewReader(f), 大于4KB用bufio.NewReaderSize(f,緩存大小)
- ?要讀Reader, 圖方便用ioutil.ReadAll()
- ?一次性讀取文件罕扎,使用ioutil.ReadFile()
- ?不同業(yè)務(wù)場景聚唐,選用不同的讀取方式
在Golang 超大文件讀取的兩個方案中提到幾個G的日志文件讀取
比如我們有一個 log 文件,運行了幾年壳影,有 100G 之大拱层。按照我們之前的操作可能代碼會這樣寫:
func ReadFile(filePath string) []byte{
content, err := ioutil.ReadFile(filePath)
if err != nil {
log.Println("Read error")
}
return content
}
上面的代碼讀取幾兆的文件可以,但是如果大于你本身及其內(nèi)存宴咧,那就直接翻車了根灯。因為上面的代碼,是把文件所有的內(nèi)容全部都讀取到內(nèi)存之后返回,幾兆的文件烙肺,你內(nèi)存夠大可以處理纳猪,但是一旦上幾百兆的文件,就沒那么好處理了桃笙。那么氏堤,正確的方法有兩種,第一個是使用流處理方式代碼如下:
func ReadFile(filePath string, handle func(string)) error {
f, err := os.Open(filePath)
defer f.Close()
if err != nil {
return err
}
buf := bufio.NewReader(f)
for {
line, err := buf.ReadLine("\n")
line = strings.TrimSpace(line)
handle(line)
if err != nil {
if err == io.EOF{
return nil
}
return err
}
return nil
}
}
第二個方案就是分片處理搏明,當(dāng)讀取的是二進制文件鼠锈,沒有換行符的時候,使用下面的方案一樣處理大文件
func ReadBigFile(fileName string, handle func([]byte)) error {
f, err := os.Open(fileName)
if err != nil {
fmt.Println("can't opened this file")
return err
}
defer f.Close()
s := make([]byte, 4096)
for {
switch nr, err := f.Read(s[:]); true {
case nr < 0:
fmt.Fprintf(os.Stderr, "cat: error reading: %s\n", err.Error())
os.Exit(1)
case nr == 0: // EOF
return nil
case nr > 0:
handle(s[0:nr])
}
}
return nil
}
bufio更多參考golang中bufio包
package test
important (
"bufio"
"io"
"os"
"regexp"
"strconv"
"strings"
"path/filepath"
"fmt"
"runtime"
"testing"
)
func TestLog(t *Testing.T) { //測試函數(shù)星著,參數(shù)必須是t *Testing.T
f,_:= os.Open("D:/file.log") //日志文件路徑
defer f.Close()
//存儲最后的結(jié)果切片购笆,因為是要找出每一個時間值,所以用切片來存儲
var resultSlice []int
//正則表達(dá)式虚循,是為了從日志文件里獲23_3executeTime:1234這種數(shù)據(jù)結(jié)果同欠。
var exp = regexp.MustCompile(`[\d]+_[\d]+(executeTime:)[\d]+`)
buf :=bufio.NewReader(f) //讀取日志文件里的字符流
for { //逐行讀取日志文件
line,err :=buf.ReadString('\n')
expArr := exp.FindAllString(line,-1)
if len(expArr) > 0 {
arr := strings.Split(expArr[0],":")
value,_ := strconv.Atoi(arr[1])
resultSlice = append(resultSlice,value)
}
if err != nil {
if err = io.EOF {
break //表示文件讀取完了
}
}
}
fmt.Println(len(resultSlice)) //打印出結(jié)果的總條數(shù)
bubbleSort(result) //對結(jié)果排序
amount :=0
for i :=0;i< len(resultSlice); i++ {
amount +=resultSlice[i]
}
average := amount/len(resultSlice)
fmt.Println("average",average,"amount",amount)
for k,v :=range resultSlice {
fmt.Println(k,v) //逐條逐條打印出時間。
}
}
//注:在這里省略了冒泡排序函數(shù)横缔。
六铺遂、寫文件
更多參考Golang讀寫文件的幾種方式
WriteFile將data寫入到filename指定的文件中。如果文件不存在茎刚,WriteFile將會創(chuàng)建該文件襟锐,且文件的權(quán)限是perm;如果文件存在斗蒋,先清空文件內(nèi)容再寫入捌斧。
content := []byte("hello golang")
//將指定內(nèi)容寫入到文件中
err := ioutil.WriteFile("output.txt", content, 0666)
if err != nil {
fmt.Println("ioutil WriteFile error: ", err)
}
追加文件內(nèi)容
func main() {
f, err := os.OpenFile("output.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777)
if err != nil {
fmt.Println("os OpenFile error: ", err)
return
}
defer f.Close()
f.WriteString("another content")
}