Golang讀取文件和處理超大文件方案

Golang 操作文件的讀取的方法很多,適用的場(chǎng)景也是各不相同,在此我們將文件的讀取分為如下幾種 :

  1. 文件整體讀取
  2. 文件分片讀取(塊級(jí)讀取)
  3. 文件行級(jí)讀取

系統(tǒng)的配置不同,執(zhí)行的耗時(shí)也不相同,此處給出一參考

系統(tǒng)配置 :

OS : Windows10

Memory : 16G

CPU (英特爾)Intel(R) Core(TM) i3-4370 CPU @ 3.80GHz(3800 MHz)

1. 文件整體讀取

文件整體讀取就是將文件一次性讀取到,理解上是將文件的內(nèi)容第一次就讀取完了

使用場(chǎng)景 :

  1. 針對(duì)小文件比較適用(大文件讀取空間和時(shí)間的消耗也很大)
  2. 對(duì)于只讀類(lèi)型的文件也比較適用(文件也不能太大)
代碼示例1
package main

import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "time"
)
// 測(cè)試用的文本文件11M大小
var m11 string = `G:\runtime\log\ccapi\11M.log`
// 測(cè)試用的文本文件400M大小
var m400 string = `G:\runtime\log\ccapi\400M.log`

// 將整個(gè)文件都讀取
func readAll(filePath string) {
    start1 := time.Now()
    ioutil.ReadFile(filePath)
    fmt.Println("readAll spend : ", time.Now().Sub(start1))
}
func main() {
    readAll(m11)
    readAll(m400)
}
$ go run main.go
readAll spend :  6.9999ms
readAll spend :  358.8014ms
代碼示例2
package main

import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "time"
)
// 測(cè)試用的文本文件11M大小
var m11 string = `G:\runtime\log\ccapi\11M.log`
// 測(cè)試用的文本文件400M大小
var m400 string = `G:\runtime\log\ccapi\400M.log`
// 將文件完整讀取
func readAllBuff(filePath string) {
    start1 := time.Now()
    // 打開(kāi)文件
    FileHandle, err := os.Open(filePath)
    if err != nil {
        log.Println(err)
        return
    }
    // 關(guān)閉文件
    defer FileHandle.Close()
    // 獲取文件當(dāng)前信息
    fileInfo, err := FileHandle.Stat()
    if err != nil {
        log.Println(err)
        return
    }
    buffer := make([]byte, fileInfo.Size())
    // 讀取文件內(nèi)容,并寫(xiě)入buffer中
    n, err := FileHandle.Read(buffer)
    if err != nil {
        log.Println(err)
    }
    // 打印所有切片中的內(nèi)容
    fmt.Println(string(buffer[:n]))
    fmt.Println("readAllBuff spend : ", time.Now().Sub(start1))
}
func main() {
    readAllBuff(m11)
    readAllBuff(m400)
}

2. 文件分片讀取

對(duì)文件一部分一部分逐步的讀取,直到文件完全讀取完

PS : 每次讀取文件的大小是根據(jù)設(shè)置的 分片 大小 ,所以對(duì)于讀取文本類(lèi)型的文件時(shí)(例如 : 日志文件)
不一定是按照你的期望逐行輸出,因?yàn)椴粫?huì)處理文本尾部的換行符,而是按照分片大小讀取內(nèi)容

使用場(chǎng)景 :

  1. 讀取超大的文件
  2. 讀二進(jìn)制類(lèi)型的文件(比如:音視頻文件或者資源類(lèi)型文件等)
代碼示例
package main

import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "time"
)
// 測(cè)試用的文本文件11M大小
var m11 string = `G:\runtime\log\ccapi\11M.log`
// 測(cè)試用的文本文件400M大小
var m400 string = `G:\runtime\log\ccapi\400M.log`
// 文件一塊一塊的讀取
func readBlock(filePath string) {
    start1 := time.Now()
    FileHandle, err := os.Open(filePath)
    if err != nil {
        log.Println(err)
        return
    }
    defer FileHandle.Close()
    // 設(shè)置每次讀取字節(jié)數(shù)
    buffer := make([]byte, 1024)
    for {
        n, err := FileHandle.Read(buffer)
        // 控制條件,根據(jù)實(shí)際調(diào)整
        if err != nil && err != io.EOF {
            log.Println(err)
        }
        if n == 0 {
            break
        }
        // 如下代碼打印出每次讀取的文件塊(字節(jié)數(shù))
        //fmt.Println(string(buffer[:n]))
    }
    fmt.Println("readBolck spend : ", time.Now().Sub(start1))
}
func main() {
    readBlock(m11)
    readBlock(m400)
}
$ go run main.go
readBolck spend :  31.9814ms
readBolck spend :  1.0889488s

3. 文件逐行讀取

對(duì)文件一行一行的讀取,直到讀到文件末尾

使用場(chǎng)景 :

  1. 讀取超大的文件很合適(例如 : 超大文本文件等)
  2. 讀取的文件最好是有換行的(如果使用單行文件組成的大文件,需要注意)
  3. 對(duì)需要分析的內(nèi)容大文件
    1. 統(tǒng)計(jì)某些數(shù)據(jù)出現(xiàn)的次數(shù)
    2. 查詢(xún)某些數(shù)據(jù)是否存在
    3. 查找指定行的數(shù)據(jù)
示例代碼1
package main

import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "time"
)
// 測(cè)試用的文本文件11M大小
var m11 string = `G:\runtime\log\ccapi\11M.log`
// 測(cè)試用的文本文件400M大小
var m400 string = `G:\runtime\log\ccapi\400M.log`
// 讀取文件的每一行
func readEachLineReader(filePath string) {
    start1 := time.Now()
    FileHandle, err := os.Open(filePath)
    if err != nil {
        log.Println(err)
        return
    }
    defer FileHandle.Close()
    lineReader := bufio.NewReader(FileHandle)
    for {
        // 相同使用場(chǎng)景下可以采用的方法
        // func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
        // func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
        // func (b *Reader) ReadString(delim byte) (line string, err error)
        line, _, err := lineReader.ReadLine()
        if err == io.EOF {
            break
        }
        // 如下是某些業(yè)務(wù)邏輯操作
        // 如下代碼打印每次讀取的文件行內(nèi)容
        fmt.Println(string(line))
    }
    fmt.Println("readEachLineReader spend : ", time.Now().Sub(start1))
}
func main(){
    readEachLineReader(m11)
    readEachLineReader(m400)
}
$ go run main.go
readEachLineReader spend :  16.9902ms
readEachLineReader spend :  537.9683ms

代碼示例2
package main

import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "time"
)
// 測(cè)試用的文本文件11M大小
var m11 string = `G:\runtime\log\ccapi\11M.log`
// 測(cè)試用的文本文件400M大小
var m400 string = `G:\runtime\log\ccapi\400M.log`
// 讀取文件的每一行
func readEachLineScanner(filePath string) {
    start1 := time.Now()
    FileHandle, err := os.Open(filePath)
    if err != nil {
        log.Println(err)
        return
    }
    defer FileHandle.Close()
    lineScanner := bufio.NewScanner(FileHandle)
    for lineScanner.Scan() {
        // 相同使用場(chǎng)景下可以使用如下方法
        // func (s *Scanner) Bytes() []byte
        // func (s *Scanner) Text() string
        // 實(shí)際邏輯 : 對(duì)讀取的內(nèi)容進(jìn)行某些業(yè)務(wù)操作
        // 如下代碼打印每次讀取的文件行內(nèi)容
        fmt.Println(lineScanner.Text())
    }
    fmt.Println("readEachLineScanner spend : ", time.Now().Sub(start1))
}
func main() {
    readEachLineScanner(m11)
    readEachLineScanner(m400)
}
$ go run main.go
readEachLineScanner spend :  17.9895ms
readEachLineScanner spend :  574.1722ms

4. 總結(jié)

  1. 面試中常見(jiàn)的類(lèi)似超大文件讀取的問(wèn)題,通常我們采用分片讀取或者逐行讀取的方案即可

    1. 大文件的上傳也可以采用類(lèi)似的解決方案 , 每次讀取文件的部分內(nèi)容上傳(寫(xiě)入)網(wǎng)絡(luò)接口中,直至文件讀取完畢
  2. 普通的小文件并且對(duì)內(nèi)容沒(méi)有太多操作的,可以采用整體讀取,速度相對(duì)較快

  3. 對(duì)文件內(nèi)容有操作的采用分片讀取和逐行讀取更合適

  4. 二進(jìn)制類(lèi)型文件采用分片讀取或者整體讀取的方案比較合適

  5. 文件讀取不僅是本地文件,要讀去網(wǎng)絡(luò)上的文件(各種文檔,音視頻,圖片,和其他各種類(lèi)型文件)時(shí)要訪(fǎng)問(wèn)到文件獲取 io.ReadCloser 或者 io.Reader 后可以采用三種方式將文件內(nèi)容讀取到

    1. func ReadAll(r io.Reader) ([]byte, error) 文件完整讀取

    2. func Copy(dst Writer, src Reader) (written int64, err error) 文件讀取并寫(xiě)入

    3. type Reader interface {
          Read(p []byte) (n int, err error)
      }
      

      通過(guò)Reader 接口的 Read 方法讀取

有疏漏 , 萬(wàn)望指正 , 不勝感謝

5. 參考資料

- [1] Golang文檔

最后編輯于
?著作權(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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