<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          怎么選擇 Go 文件讀取方案

          共 994字,需瀏覽 2分鐘

           ·

          2022-03-08 21:14

          點(diǎn)擊上方“Go語(yǔ)言進(jìn)階學(xué)習(xí)”,進(jìn)行關(guān)注

          回復(fù)“Go語(yǔ)言”即可獲贈(zèng)Python從入門(mén)到進(jìn)階共10本電子書(shū)

          離離原上草,一歲一枯榮。

          文件處理是一個(gè)常見(jiàn)的問(wèn)題,同時(shí) Go 又提供了非常多的文件讀取方法,容易讓人患選擇困難癥。之前我們轉(zhuǎn)過(guò)一篇超全總結(jié):Go 讀文件的 10 種方法的文章,列舉了10 余種讀取方式。本文作為其擴(kuò)展,以實(shí)際不同大小的文件為例,來(lái)具體比較下它們的差異。

          創(chuàng)建不同大小的文件

          首先,我們需要有比較對(duì)象。鑒于電腦磁盤(pán)空間有限,本文就比較 KB、MB、GB 三個(gè)級(jí)別的文件讀取差異。

          package?main

          import?(
          ?"bufio"
          ?"math/rand"
          ?"os"
          ?"time"
          )

          const?charset?=?"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

          var?seededRand?=?rand.New(rand.NewSource(time.Now().UnixNano()))

          func?StringWithCharset(length?int)?string?{
          ?b?:=?make([]byte,?length)
          ?for?i?:=?range?b?{
          ??b[i]?=?charset[seededRand.Intn(len(charset))]
          ?}
          ?return?string(b)
          }

          func?main()?{
          ?files?:=?map[string]int{"4KB.txt":?4,?"4MB.txt":?4096,?"4GB.txt":?4194304,?"16GB.txt":?16777216}
          ?for?name,?number?:=?range?files?{
          ??file,?err?:=?os.OpenFile(name,?os.O_WRONLY|os.O_CREATE,?0666)
          ??if?err?!=?nil?{
          ???panic(err)
          ??}
          ??write?:=?bufio.NewWriter(file)
          ??for?i?:=?0;?i????s?:=?StringWithCharset(1023)?+?"\n"
          ???write.WriteString(s)
          ??}
          ??file.Close()
          ?}
          }

          執(zhí)行以上代碼,我們依次得到 4KB、4MB、4GB、16GB 大小的文件,它們是由每行 1KB 大小隨機(jī)字符串的內(nèi)容組成。

          $?ls?-alh?4kb.txt?4MB.txt?4GB.txt?16GB.txt
          -rw-r--r--??1?slp??staff????16G?Mar??6?15:57?16GB.txt
          -rw-r--r--??1?slp??staff???4.0G?Mar??6?15:54?4GB.txt
          -rw-r--r--??1?slp??staff???4.0M?Mar??6?15:53?4MB.txt
          -rw-r--r--??1?slp??staff???4.0K?Mar??6?15:16?4kb.txt

          接下來(lái),我們使用不同的方式來(lái)讀取這些文件內(nèi)容。

          整個(gè)文件加載

          Go 提供了可一次性讀取文件內(nèi)容的方法:os.ReadFile 與 ioutil.ReadFile。在 Go 1.16 開(kāi)始,ioutil.ReadFile 就等價(jià)于 os.ReadFile。

          func?BenchmarkOsReadFile4KB(b?*testing.B)?{
          ?for?i?:=?0;?i???_,?err?:=?os.ReadFile("./4KB.txt")
          ??if?err?!=?nil?{
          ???b.Fatal(err)
          ??}
          ?}
          }

          func?BenchmarkOsReadFile4MB(b?*testing.B)?{
          ?for?i?:=?0;?i???_,?err?:=?os.ReadFile("./4MB.txt")
          ??if?err?!=?nil?{
          ???b.Fatal(err)
          ??}
          ?}
          }

          func?BenchmarkOsReadFile4GB(b?*testing.B)?{
          ?for?i?:=?0;?i???_,?err?:=?os.ReadFile("./4GB.txt")
          ??if?err?!=?nil?{
          ???b.Fatal(err)
          ??}
          ?}
          }

          func?BenchmarkOsReadFile16GB(b?*testing.B)?{
          ?for?i?:=?0;?i???_,?err?:=?os.ReadFile("./16GB.txt")
          ??if?err?!=?nil?{
          ???b.Fatal(err)
          ??}
          ?}
          }

          一次性加載文件的優(yōu)缺點(diǎn)非常明顯,它能減少 IO 次數(shù),但它會(huì)將文件內(nèi)容都加載至內(nèi)存中,對(duì)于大文件,存在內(nèi)存撐爆的風(fēng)險(xiǎn)。

          逐行讀取

          在很多情況下,例如日志分析,對(duì)文件的處理都是按行進(jìn)行的。Go 中 bufio.Reader 對(duì)象提供了一個(gè) ReadLine() 方法,但其實(shí)我們更多地是使用 ReadBytes('\n') 或者 ReadString('\n') 代替。

          //?ReadLine?is?a?low-level?line-reading?primitive.?Most?callers?should?use
          //?ReadBytes('\n')?or?ReadString('\n')?instead?or?use?a?Scanner.

          我們以 ReadString('\n') 為例,對(duì) 4 個(gè)文件分別進(jìn)行逐行讀取

          func?ReadLines(filename?string)?{
          ?fi,?err?:=?os.Open(filename)
          ?if?err?!=?nil{
          ??panic(err)
          ?}
          ?defer?fi.Close()
          ?reader?:=?bufio.NewReader(fi)
          ?for?{
          ??_,?err?=?reader.ReadString('\n')
          ??if?err?!=?nil?{
          ???if?err?==?io.EOF?{
          ????break
          ???}
          ???panic(err)
          ??}
          ?}
          }

          func?BenchmarkReadLines4KB(b?*testing.B)?{
          ?for?i?:=?0;?i???ReadLines("./4KB.txt")
          ?}
          }

          func?BenchmarkReadLines4MB(b?*testing.B)?{
          ?for?i?:=?0;?i???ReadLines("./4MB.txt")
          ?}
          }

          func?BenchmarkReadLines4GB(b?*testing.B)?{
          ?for?i?:=?0;?i???ReadLines("./4GB.txt")
          ?}
          }

          func?BenchmarkReadLines16GB(b?*testing.B)?{
          ?for?i?:=?0;?i???ReadLines("./16GB.txt")
          ?}
          }

          塊讀取

          塊讀取也稱(chēng)為分片讀取,這也很好理解,我們可以將內(nèi)容分成一塊塊的,每次讀取指定大小的塊內(nèi)容。這里,我們將塊大小設(shè)置為 4KB。

          func?ReadChunk(filename?string)?{
          ?f,?err?:=?os.Open(filename)
          ?if?err?!=?nil?{
          ??panic(err)
          ?}
          ?defer?f.Close()
          ?buf?:=?make([]byte,?4*1024)
          ?r?:=?bufio.NewReader(f)
          ?for?{
          ??_,?err?=?r.Read(buf)
          ??if?err?!=?nil?{
          ???if?err?==?io.EOF?{
          ????break
          ???}
          ???panic(err)
          ??}
          ?}
          }

          func?BenchmarkReadChunk4KB(b?*testing.B)?{
          ?for?i?:=?0;?i???ReadChunk("./4KB.txt")
          ?}
          }

          func?BenchmarkReadChunk4MB(b?*testing.B)?{
          ?for?i?:=?0;?i???ReadChunk("./4MB.txt")
          ?}
          }

          func?BenchmarkReadChunk4GB(b?*testing.B)?{
          ?for?i?:=?0;?i???ReadChunk("./4GB.txt")
          ?}
          }

          func?BenchmarkReadChunk16GB(b?*testing.B)?{
          ?for?i?:=?0;?i???ReadChunk("./16GB.txt")
          ?}
          }

          匯總結(jié)果

          BenchmarkOsReadFile4KB-8???????????92877?????????????12491?ns/op
          BenchmarkOsReadFile4MB-8????????????1620????????????744460?ns/op
          BenchmarkOsReadFile4GB-8???????????????1????????7518057733?ns/op
          signal:?killed

          BenchmarkReadLines4KB-8????????????90846?????????????13184?ns/op
          BenchmarkReadLines4MB-8??????????????493???????????2338170?ns/op
          BenchmarkReadLines4GB-8????????????????1????????3072629047?ns/op
          BenchmarkReadLines16GB-8???????????????1????????12472749187?ns/op

          BenchmarkReadChunk4KB-8????????????99848?????????????12262?ns/op
          BenchmarkReadChunk4MB-8??????????????913???????????1233216?ns/op
          BenchmarkReadChunk4GB-8????????????????1????????2095515009?ns/op
          BenchmarkReadChunk16GB-8???????????????1????????8547054349?ns/op

          在本文的測(cè)試條件下(每行數(shù)據(jù) 1KB),對(duì)于小對(duì)象 4KB 的讀取,三種方式差距并不大;在 MB 級(jí)別的讀取中,直接加載最快,但塊讀取也慢不了多少;上了 GB 后,塊讀取方式會(huì)最快。

          且有一點(diǎn)可以注意到的是,在整個(gè)文件加載的方式中,對(duì)于 16 GB 的文件數(shù)據(jù)(測(cè)試機(jī)器運(yùn)行內(nèi)存為 8GB),會(huì)內(nèi)存耗盡出錯(cuò),沒(méi)法執(zhí)行。

          總結(jié)

          不管是什么大小的文件,均不推薦整個(gè)文件加載的方式,因?yàn)樗谛∥募r(shí)的速度優(yōu)勢(shì)并沒(méi)有那么大,相較于安全隱患,不值得選擇它。

          塊讀取是優(yōu)先選擇,尤其對(duì)于一些沒(méi)有換行符的文件,例如音視頻等。通過(guò)設(shè)定合適的塊讀取大小,能讓速度和內(nèi)存得到很好的平衡。且在讀取過(guò)程中,往往伴隨著處理內(nèi)容的邏輯。每塊內(nèi)容可以賦給一個(gè)工作 goroutine 來(lái)處理,能更好地并發(fā)。



          -------------------?End?-------------------

          歷史文章推薦:

          歡迎大家點(diǎn)贊轉(zhuǎn)發(fā),轉(zhuǎn)載,感謝大家的相伴與支持

          想加入學(xué)習(xí)群請(qǐng)?jiān)诤笈_(tái)回復(fù)【入群

          萬(wàn)水千山總是情,點(diǎn)個(gè)【在看】行不行

          瀏覽 43
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  思思热精品在线视频 | 哪些网站能看毛片 | 天堂A片| 九九精品重口味三级在线视频 | 69精品久久久久中文字幕 |