Golang 文件操作

參考
Golang文件操作整理
golang中的文件讀寫

一、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)建

1.參考os.Open()和os.OpenFile()

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

參考go判斷文件夾是否存在,并創(chuàng)建

// 判斷文件夾是否存在
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包

參考用GO語言提取日志文件里的信息

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

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Lua 5.1 參考手冊(cè) by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 14,246評(píng)論 0 38
  • 本文為轉(zhuǎn)載,原文:Golang 學(xué)習(xí)筆記(08)—— 文件操作 path 在path包中封裝了一些路徑相關(guān)的操作,...
    ChainZhang閱讀 3,239評(píng)論 0 4
  • Truncate文件 得到文件信息 重命名和移動(dòng) 譯者按: rename 和 move 原理一樣 刪除文件 打開和...
    黑手黨老k閱讀 2,359評(píng)論 0 2
  • 滑雪后的某一天、冬陽約我看電影、電影票是朋友給的、我說下班比較晚、冬陽說那就定晚點(diǎn)時(shí)間看、他在家等我。 想想要和冬...
    泰夢魚閱讀 222評(píng)論 0 2
  • 又是全新的一天,早起無事,聽著別人的故事,梳理著自己的情緒,不知不覺就走完了全程。不著急不功利,任憑車走,任憑景流...
    一星若月閱讀 262評(píng)論 0 0

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