一、API
參考Go語言學(xué)習(xí)筆記(五)文件操作
1.os.File
type File
File代表一個(gè)打開的文件對(duì)象。func Create(name string) (file *File, err error)
Create采用模式0666(任何人都可讀寫,不可執(zhí)行)創(chuàng)建一個(gè)名為name的文件,如果文件已存在會(huì)截?cái)嗨榭瘴募?。如果成功,返回的文件?duì)象可用于I/O;對(duì)應(yīng)的文件描述符具有O_RDWR模式。如果出錯(cuò),錯(cuò)誤底層類型是*PathError。func Open(name string) (file *File, err error)
Open打開一個(gè)文件用于讀取。如果操作成功,返回的文件對(duì)象的方法可用于讀取數(shù)據(jù);對(duì)應(yīng)的文件描述符具有O_RDONLY模式。如果出錯(cuò),錯(cuò)誤底層類型是*PathError。func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
OpenFile是一個(gè)更一般性的文件打開函數(shù),大多數(shù)調(diào)用者都應(yīng)用Open或Create代替本函數(shù)。它會(huì)使用指定的選項(xiàng)(如O_RDONLY等)、指定的模式(如0666等)打開指定名稱的文件。如果操作成功,返回的文件對(duì)象可用于I/O。如果出錯(cuò),錯(cuò)誤底層類型是*PathError。func NewFile(fd uintptr, name string) *File
NewFile使用給出的Unix文件描述符和名稱創(chuàng)建一個(gè)文件。func Pipe() (r *File, w *File, err error)
Pipe返回一對(duì)關(guān)聯(lián)的文件對(duì)象。從r的讀取將返回寫入w的數(shù)據(jù)。本函數(shù)會(huì)返回兩個(gè)文件對(duì)象和可能的錯(cuò)誤。func (f *File) Name() string
Name方法返回(提供給Open/Create等方法的)文件名稱。func (f *File) Stat() (fi FileInfo, err error)
Stat返回描述文件f的FileInfo類型值。如果出錯(cuò),錯(cuò)誤底層類型是*PathError。func (f *File) Fd() uintptr
Fd返回與文件f對(duì)應(yīng)的整數(shù)類型的Unix文件描述符。func (f *File) Chdir() error
Chdir將當(dāng)前工作目錄修改為f,f必須是一個(gè)目錄。如果出錯(cuò),錯(cuò)誤底層類型是*PathError。func (f *File) Chmod(mode FileMode) error
Chmod修改文件的模式。如果出錯(cuò),錯(cuò)誤底層類型是*PathError。func (f *File) Chown(uid, gid int) error
Chown修改文件的用戶ID和組ID。如果出錯(cuò),錯(cuò)誤底層類型是*PathError。func (f *File) Readdir(n int) (fi []FileInfo, err error)
Readdir讀取目錄f的內(nèi)容,返回一個(gè)有n個(gè)成員的[]FileInfo,這些FileInfo是被Lstat返回的,采用目錄順序。對(duì)本函數(shù)的下一次調(diào)用會(huì)返回上一次調(diào)用剩余未讀取的內(nèi)容的信息。
如果n>0,Readdir函數(shù)會(huì)返回一個(gè)最多n個(gè)成員的切片。這時(shí),如果Readdir返回一個(gè)空切片,它會(huì)返回一個(gè)非nil的錯(cuò)誤說明原因。如果到達(dá)了目錄f的結(jié)尾,返回值err會(huì)是io.EOF。
如果n<=0,Readdir函數(shù)返回目錄中剩余所有文件對(duì)象的FileInfo構(gòu)成的切片。此時(shí),如果Readdir調(diào)用成功(讀取所有內(nèi)容直到結(jié)尾),它會(huì)返回該切片和nil的錯(cuò)誤值。如果在到達(dá)結(jié)尾前遇到錯(cuò)誤,會(huì)返回之前成功讀取的FileInfo構(gòu)成的切片和該錯(cuò)誤。func (f *File) Readdirnames(n int) (names []string, err error)
Readdir讀取目錄f的內(nèi)容,返回一個(gè)有n個(gè)成員的[]string,切片成員為目錄中文件對(duì)象的名字,采用目錄順序。對(duì)本函數(shù)的下一次調(diào)用會(huì)返回上一次調(diào)用剩余未讀取的內(nèi)容的信息。
如果n>0,Readdir函數(shù)會(huì)返回一個(gè)最多n個(gè)成員的切片。這時(shí),如果Readdir返回一個(gè)空切片,它會(huì)返回一個(gè)非nil的錯(cuò)誤說明原因。如果到達(dá)了目錄f的結(jié)尾,返回值err會(huì)是io.EOF。
如果n<=0,Readdir函數(shù)返回目錄中剩余所有文件對(duì)象的名字構(gòu)成的切片。此時(shí),如果Readdir調(diào)用成功(讀取所有內(nèi)容直到結(jié)尾),它會(huì)返回該切片和nil的錯(cuò)誤值。如果在到達(dá)結(jié)尾前遇到錯(cuò)誤,會(huì)返回之前成功讀取的名字構(gòu)成的切片和該錯(cuò)誤。func (f *File) Truncate(size int64) error
Truncate改變文件的大小,它不會(huì)改變I/O的當(dāng)前位置。 如果截?cái)辔募喑龅牟糠志蜁?huì)被丟棄。如果出錯(cuò),錯(cuò)誤底層類型是*PathError。func (f *File) Read(b []byte) (n int, err error)
Read方法從f中讀取最多l(xiāng)en(b)字節(jié)數(shù)據(jù)并寫入b。它返回讀取的字節(jié)數(shù)和可能遇到的任何錯(cuò)誤。文件終止標(biāo)志是讀取0個(gè)字節(jié)且返回值err為io.EOF。func (f *File) ReadAt(b []byte, off int64) (n int, err error)
ReadAt從指定的位置(相對(duì)于文件開始位置)讀取len(b)字節(jié)數(shù)據(jù)并寫入b。它返回讀取的字節(jié)數(shù)和可能遇到的任何錯(cuò)誤。當(dāng)n<len(b)時(shí),本方法總是會(huì)返回錯(cuò)誤;如果是因?yàn)榈竭_(dá)文件結(jié)尾,返回值err會(huì)是io.EOF。func (f *File) Write(b []byte) (n int, err error)
Write向文件中寫入len(b)字節(jié)數(shù)據(jù)。它返回寫入的字節(jié)數(shù)和可能遇到的任何錯(cuò)誤。如果返回值n!=len(b),本方法會(huì)返回一個(gè)非nil的錯(cuò)誤。func (f *File) WriteString(s string) (ret int, err error)
WriteString類似Write,但接受一個(gè)字符串參數(shù)。func (f *File) WriteAt(b []byte, off int64) (n int, err error)
WriteAt在指定的位置(相對(duì)于文件開始位置)寫入len(b)字節(jié)數(shù)據(jù)。它返回寫入的字節(jié)數(shù)和可能遇到的任何錯(cuò)誤。如果返回值n!=len(b),本方法會(huì)返回一個(gè)非nil的錯(cuò)誤。func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek設(shè)置下一次讀/寫的位置。offset為相對(duì)偏移量,而whence決定相對(duì)位置:0為相對(duì)文件開頭,1為相對(duì)當(dāng)前位置,2為相對(duì)文件結(jié)尾。它返回新的偏移量(相對(duì)開頭)和可能的錯(cuò)誤。func (f *File) Sync() (err error)
Sync遞交文件的當(dāng)前內(nèi)容進(jìn)行穩(wěn)定的存儲(chǔ)。一般來說,這表示將文件系統(tǒng)的最近寫入的數(shù)據(jù)在內(nèi)存中的拷貝刷新到硬盤中穩(wěn)定保存。func (f *File) Close() error
Close關(guān)閉文件f,使文件不能用于讀寫。它返回可能出現(xiàn)的錯(cuò)誤。
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í)將數(shù)據(jù)附加到文件尾部
O_CREATE int = syscall.O_CREAT // 如果不存在將創(chuàng)建一個(gè)新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必須不存在
O_SYNC int = syscall.O_SYNC // 打開文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打開時(shí)清空文件
)
二、讀,創(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")
}
}
}
四、刪除
文件刪除的時(shí)候,不管是普通文件還是目錄文件,都可以用err:=os.Remove(filename)這樣的操作來執(zhí)行。當(dāng)然要是想移除整個(gè)文件夾,直接使用RemoveAll(path string)操作即可??梢钥匆幌翿emoveAll函數(shù)的內(nèi)部實(shí)現(xiàn),整體上就是遍歷,遞歸的操作過程,其他的類似的文件操作都可以用類似的模板來實(shí)現(xiàn)
os.RemoveAll("./gzFiles2")
五、讀文件
這一部分較多的涉及I/O的相關(guān)操作,系統(tǒng)的介紹放在I/O那部分來整理,大體上向文件中讀寫內(nèi)容的時(shí)候有三種方式:
1、在使用f, err := os.Open(file_path)打開文件之后直接使用 f.read() f.write() 結(jié)合自定義的buffer每次從文件中讀入/讀出固定的內(nèi)容
2、使用ioutl的readFile和writeFile方法
3、使用bufio采用帶有緩存的方式進(jìn)行讀寫,比如通過info:=bufio.NewReader(f)將實(shí)現(xiàn)了io.Reader的接口的實(shí)例加載上來之后,就可以使用info.ReadLine()來每次實(shí)現(xiàn)一整行的讀取,直到err信息為io.EOF時(shí),讀取結(jié)束
Golang幾種讀文件方式的比較對(duì)三種文件操作的讀入速度進(jìn)行了比較
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))
}
運(yùn)行命令go run read.go filename, 指定需要讀取的文件就可以測試了。
在golang bufio、ioutil讀文件的速度比較(性能測試)和影響因素分析 中,給出了結(jié)論:
在查閱golang標(biāo)準(zhǔn)庫的源代碼后,之所以有不同的結(jié)果是與每個(gè)方法的實(shí)現(xiàn)相關(guān)的,最大的因素就是內(nèi)部buffer的大小,這個(gè)直接決定了讀取的快慢:
- 1.f.Read()底層實(shí)現(xiàn)是系統(tǒng)調(diào)用syscall.Read(),沒有深究
- 2.bufio.NewReader(f)實(shí)際調(diào)用NewReaderSize(f, defaultBufSize),而defaultBufSize=4096,可以直接用bufio.NewReaderSize(f,32768)來預(yù)分配更大的緩存,緩存的實(shí)質(zhì)是make([]byte, size)
- 3.ioutil.ReadAll(f)實(shí)際調(diào)用readAll(r, bytes.MinRead),而bytes.MinRead=512,緩存的實(shí)質(zhì)是bytes.NewBuffer(make([]byte, 0, 512),雖然bytes.Buffer會(huì)根據(jù)情況自動(dòng)增大,但每次重新分配都會(huì)影響性能
- 4.ioutil.ReadFile(path)是調(diào)用readAll(f, n+bytes.MinRead),這個(gè)n取決于文件大小,文件小于10^9字節(jié)(0.93GB),n=文件大小,就是NewBuffer一個(gè)略大于文件大小的緩存,非??犊?;大于則n=0,好慘,也就是說大于1G的文件就跟ioutil.ReadAll(f)一個(gè)樣子了。
- 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è)方案中提到幾個(gè)G的日志文件讀取
比如我們有一個(gè) log 文件,運(yùn)行了幾年,有 100G 之大。按照我們之前的操作可能代碼會(huì)這樣寫:
func ReadFile(filePath string) []byte{
content, err := ioutil.ReadFile(filePath)
if err != nil {
log.Println("Read error")
}
return content
}
上面的代碼讀取幾兆的文件可以,但是如果大于你本身及其內(nèi)存,那就直接翻車了。因?yàn)樯厦娴拇a,是把文件所有的內(nèi)容全部都讀取到內(nèi)存之后返回,幾兆的文件,你內(nèi)存夠大可以處理,但是一旦上幾百兆的文件,就沒那么好處理了。那么,正確的方法有兩種,第一個(gè)是使用流處理方式代碼如下:
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
}
}
第二個(gè)方案就是分片處理,當(dāng)讀取的是二進(jìn)制文件,沒有換行符的時(shí)候,使用下面的方案一樣處理大文件
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()
//存儲(chǔ)最后的結(jié)果切片,因?yàn)槭且页雒恳粋€(gè)時(shí)間值,所以用切片來存儲(chǔ)
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) //對(duì)結(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í)間。
}
}
//注:在這里省略了冒泡排序函數(shù)。
六、寫文件
更多參考Golang讀寫文件的幾種方式
WriteFile將data寫入到filename指定的文件中。如果文件不存在,WriteFile將會(huì)創(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")
}