最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go語言文件讀取的一些總結(jié)

 更新時(shí)間:2020年08月20日 09:54:31   作者:禹鼎侯  
這篇文章主要介紹了Go語言文件讀取的一些總結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

Go語言在進(jìn)行文件操作的時(shí)候,可以有多種方法。最常見的比如直接對文件本身進(jìn)行Read和Write; 除此之外,還可以使用bufio庫的流式處理以及分片式處理;如果文件較小,使用ioutil也不失為一種方法。

面對這么多的文件處理的方式,那么初學(xué)者可能就會有困惑:我到底該用那種?它們之間有什么區(qū)別?筆者試著從文件讀取來對go語言的幾種文件處理方式進(jìn)行分析。

os.File、bufio、ioutil比較

效率測試

文件的讀取效率是所有開發(fā)者都會關(guān)心的話題,尤其是當(dāng)文件特別大的時(shí)候。為了盡可能的展示這三者對文件讀取的性能,我準(zhǔn)備了三個(gè)文件,分別為small.txt,midium.txt、large.txt,分別對應(yīng)KB級別、MB級別和GB級別。

這三個(gè)文件大小分別為4KB、21MB、1GB。其中內(nèi)容是比較常規(guī)的json格式的文本。

測試代碼如下:

//使用File自帶的Read
func read1(filename string) int {
 fi, err := os.Open(filename)
 if err != nil {
  panic(err)
 }
 defer fi.Close()
 buf := make([]byte, 4096)
 var nbytes int
 for {
  n, err := fi.Read(buf)
  if err != nil && err != io.EOF {
   panic(err)
  }
  if n == 0 {
   break
  }
  nbytes += n

 }
 return nbytes
}

read1函數(shù)使用的是os庫對文件進(jìn)行直接操作,為了確定確實(shí)都到了文件內(nèi)容,并將讀到的大小字節(jié)數(shù)返回。

//使用bufio
func read2(filename string) int {
 fi, err := os.Open(filename)
 if err != nil {
  panic(err)
 }
 defer fi.Close()
 buf := make([]byte, 4096)
 var nbytes int
 rd := bufio.NewReader(fi)
 for {
  n, err := rd.Read(buf)
  if err != nil && err != io.EOF {
   panic(err)
  }
  if n == 0 {
   break
  }
  nbytes += n
 }
 return nbytes
}

read2函數(shù)使用的是bufio庫,操作NewReader對文件進(jìn)行流式處理,和前面一樣,為了確定確實(shí)都到了文件內(nèi)容,并將讀到的大小字節(jié)數(shù)返回。

//使用ioutil
func read3(filename string) int {
 fi, err := os.Open(filename)
 if err != nil {
  panic(err)
 }
 defer fi.Close()
 fd, err := ioutil.ReadAll(fi)
 nbytes := len(fd)
 return nbytes
}

read3函數(shù)是使用ioutil庫進(jìn)行文件讀取,這種方式比較暴力,直接將文件內(nèi)容一次性全部讀到內(nèi)存中,然后對內(nèi)存中的文件內(nèi)容進(jìn)行相關(guān)的操作。

我們使用如下的測試代碼進(jìn)行測試:

func testfile1(filename string) {
 fmt.Printf("============test1 %s ===========\n", filename)
 start := time.Now()
 size1 := read1(filename)
 t1 := time.Now()
 fmt.Printf("Read 1 cost: %v, size: %d\n", t1.Sub(start), size1)
 size2 := read2(filename)
 t2 := time.Now()
 fmt.Printf("Read 2 cost: %v, size: %d\n", t2.Sub(t1), size2)
 size3 := read3(filename)
 t3 := time.Now()
 fmt.Printf("Read 3 cost: %v, size: %d\n", t3.Sub(t2), size3)
}

在main函數(shù)中調(diào)用如下:

func main() {
 testfile1("small.txt")
 testfile1("midium.txt")
 testfile1("large.txt")
 // testfile2("small.txt")
 // testfile2("midium.txt")
 // testfile2("large.txt")
}

測試結(jié)果如下所示:

從以上結(jié)果可知:

  • 當(dāng)文件較?。↘B級別)時(shí),ioutil > bufio > os。
  • 當(dāng)文件大小比較常規(guī)(MB級別)時(shí),三者差別不大,但bufio又是已經(jīng)顯現(xiàn)出來。
  • 當(dāng)文件較大(GB級別)時(shí),bufio > os > ioutil。

原因分析

為什么會出現(xiàn)上面的不同結(jié)果?

其實(shí)ioutil最好理解,當(dāng)文件較小時(shí),ioutil使用ReadAll函數(shù)將文件中所有內(nèi)容直接讀入內(nèi)存,只進(jìn)行了一次io操作,但是os和bufio都是進(jìn)行了多次讀取,才將文件處理完,所以ioutil肯定要快于os和bufio的。

但是隨著文件的增大,達(dá)到接近GB級別時(shí),ioutil直接讀入內(nèi)存的弊端就顯現(xiàn)出來,要將GB級別的文件內(nèi)容全部讀入內(nèi)存,也就意味著要開辟一塊GB大小的內(nèi)存用來存放文件數(shù)據(jù),這對內(nèi)存的消耗是非常大的,因此效率就慢了下來。

如果文件繼續(xù)增大,達(dá)到3GB甚至以上,ioutil這種讀取方式就完全無能為力了。(一個(gè)單獨(dú)的進(jìn)程空間為4GB,真正存放數(shù)據(jù)的堆區(qū)和棧區(qū)更是遠(yuǎn)遠(yuǎn)小于4GB)。

而os為什么在面對大文件時(shí),效率會低于bufio?通過查看bufio的NewReader源碼不難發(fā)現(xiàn),在NewReader里,默認(rèn)為我們提供了一個(gè)大小為4096的緩沖區(qū),所以系統(tǒng)調(diào)用會每次先讀取4096字節(jié)到緩沖區(qū),然后rd.Read會從緩沖區(qū)去讀取。

const (
 defaultBufSize = 4096
)

func NewReader(rd io.Reader) *Reader {
 return NewReaderSize(rd, defaultBufSize)
}

func NewReaderSize(rd io.Reader, size int) *Reader {
 // Is it already a Reader?
 b, ok := rd.(*Reader)
 if ok && len(b.buf) >= size {
  return b
 }
 if size < minReadBufferSize {
  size = minReadBufferSize
 }
 r := new(Reader)
 r.reset(make([]byte, size), rd)
 return r
}

而os因?yàn)樯倭诉@一層緩沖區(qū),每次讀取,都會執(zhí)行系統(tǒng)調(diào)用,因此內(nèi)核頻繁的在用戶態(tài)和內(nèi)核態(tài)之間切換,而這種切換,也是需要消耗的,故而會慢于bufio的讀取方式。

筆者翻閱網(wǎng)上資料,關(guān)于緩沖,有內(nèi)核中的緩沖和進(jìn)程中的緩沖兩種,其中,內(nèi)核中的緩沖是內(nèi)核提供的,即系統(tǒng)對磁盤提供一個(gè)緩沖區(qū),不管有沒有提供進(jìn)程中的緩沖,內(nèi)核緩沖都是存在的。

而進(jìn)程中的緩沖是對輸入輸出流做了一定的改進(jìn),提供的一種流緩沖,它在讀寫操作發(fā)生時(shí),先將數(shù)據(jù)存入流緩沖中,只有當(dāng)流緩沖區(qū)滿了或者刷新(如調(diào)用flush函數(shù))時(shí),才將數(shù)據(jù)取出,送往內(nèi)核緩沖區(qū),它起到了一定的保護(hù)內(nèi)核的作用。
因此,我們不難發(fā)現(xiàn),os是典型的內(nèi)核中的緩沖,而bufio和ioutil都屬于進(jìn)程中的緩沖。

總結(jié)

當(dāng)讀取小文件時(shí),使用ioutil效率明顯優(yōu)于os和bufio,但如果是大文件,bufio讀取會更快。

讀取一行數(shù)據(jù)

前面簡要分析了go語言三種不同讀取文件方式之間的區(qū)別。但實(shí)際的開發(fā)中,我們對文件的讀取往往是以行為單位的,即每次讀取一行進(jìn)行處理。

go語言并沒有像C語言一樣給我們提供好了類似于fgets這樣的函數(shù)可以正好讀取一行內(nèi)容,因此,需要自己去實(shí)現(xiàn)。
從前面的對比分析可以知道,無論是處理大文件還是小文件,bufio始終是最為平滑和高效的,因此我們考慮使用bufio庫進(jìn)行處理。

翻閱bufio庫的源碼,發(fā)現(xiàn)可以使用如下幾種方式進(jìn)行讀取一行文件的處理:

  • ReadBytes
  • ReadString
  • ReadSlice
  • ReadLine

效率測試

在討論這四種讀取一行文件操作的函數(shù)之前,仍然做一下效率測試。
測試代碼如下:

func readline1(filename string) {
 fi, err := os.Open(filename)
 if err != nil {
  panic(err)
 }
 defer fi.Close()
 rd := bufio.NewReader(fi)
 for {
  _, err := rd.ReadBytes('\n')
  if err != nil || err == io.EOF {
   break
  }
 }
}
func readline2(filename string) {
 fi, err := os.Open(filename)
 if err != nil {
  panic(err)
 }
 defer fi.Close()
 rd := bufio.NewReader(fi)
 for {
  _, err := rd.ReadString('\n')
  if err != nil || err == io.EOF {
   break
  }
 }
}
func readline3(filename string) {
 fi, err := os.Open(filename)
 if err != nil {
  panic(err)
 }
 defer fi.Close()
 rd := bufio.NewReader(fi)
 for {
  _, err := rd.ReadSlice('\n')
  if err != nil || err == io.EOF {
   break
  }
 }
}
func readline4(filename string) {
 fi, err := os.Open(filename)
 if err != nil {
  panic(err)
 }
 defer fi.Close()
 rd := bufio.NewReader(fi)
 for {
  _, _, err := rd.ReadLine()
  if err != nil || err == io.EOF {
   break
  }
 }
}

可以看到,這四種操作方式,無論是函數(shù)調(diào)用,還是函數(shù)返回值的處理,其實(shí)都是大同小異的。但通過測試效率,則可以看出它們之間的區(qū)別。

我們使用下面的測試代碼:

func testfile2(filename string) {
 fmt.Printf("============test2 %s ===========\n", filename)
 start := time.Now()
 readline1(filename)
 t1 := time.Now()
 fmt.Printf("Readline 1 cost: %v\n", t1.Sub(start))
 readline2(filename)
 t2 := time.Now()
 fmt.Printf("Readline 2 cost: %v\n", t2.Sub(t1))
 readline3(filename)
 t3 := time.Now()
 fmt.Printf("Readline 3 cost: %v\n", t3.Sub(t2))
 readline4(filename)
 t4 := time.Now()
 fmt.Printf("Readline 4 cost: %v\n", t4.Sub(t3))
}

在main函數(shù)中調(diào)用如下:

func main() {
 // testfile1("small.txt")
 // testfile1("midium.txt")
 // testfile1("large.txt")
 testfile2("small.txt")
 testfile2("midium.txt")
 testfile2("large.txt")
}

運(yùn)行結(jié)果如下所示:

通過現(xiàn)象,除了small.txt之外,大致可以分為兩組:

  • ReadBytes對小文件處理效率最差
  • 在處理大文件時(shí),ReadLine和ReadSlice效率相近,要明顯快于ReadString和ReadBytes。

原因分析

為什么會出現(xiàn)上面的現(xiàn)象,不防從源碼層面進(jìn)行分析。
通過閱讀源碼,我們發(fā)現(xiàn)這四個(gè)函數(shù)之間存在這樣一個(gè)關(guān)系:

  • ReadLine <- (調(diào)用) ReadSlice
  • ReadString <- (調(diào)用)ReadBytes<-(調(diào)用)ReadSlice

既然如此,那為什么在處理大文件時(shí),ReadLine效率要明顯高于ReadBytes呢?

首先,我們要知道,ReadSlice是切片式讀取,即根據(jù)分隔符去進(jìn)行切片。
通過源碼發(fā)下,ReadLine只是在切片讀取的基礎(chǔ)上,對換行符\n和\r\n做了一些處理:

func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {
 line, err = b.ReadSlice('\n')
 if err == ErrBufferFull {
  // Handle the case where "\r\n" straddles the buffer.
  if len(line) > 0 && line[len(line)-1] == '\r' {
   // Put the '\r' back on buf and drop it from line.
   // Let the next call to ReadLine check for "\r\n".
   if b.r == 0 {
    // should be unreachable
    panic("bufio: tried to rewind past start of buffer")
   }
   b.r--
   line = line[:len(line)-1]
  }
  return line, true, nil
 }

 if len(line) == 0 {
  if err != nil {
   line = nil
  }
  return
 }
 err = nil

 if line[len(line)-1] == '\n' {
  drop := 1
  if len(line) > 1 && line[len(line)-2] == '\r' {
   drop = 2
  }
  line = line[:len(line)-drop]
 }
 return
}

而ReadBytes則是通過append先將讀取的內(nèi)容暫存到full數(shù)組中,最后再copy出來,append和copy都是要消耗內(nèi)存和io的,因此效率自然就慢了。其源碼如下所示:

func (b *Reader) ReadBytes(delim byte) ([]byte, error) {
 // Use ReadSlice to look for array,
 // accumulating full buffers.
 var frag []byte
 var full [][]byte
 var err error
 n := 0
 for {
  var e error
  frag, e = b.ReadSlice(delim)
  if e == nil { // got final fragment
   break
  }
  if e != ErrBufferFull { // unexpected error
   err = e
   break
  }

  // Make a copy of the buffer.
  buf := make([]byte, len(frag))
  copy(buf, frag)
  full = append(full, buf)
  n += len(buf)
 }

 n += len(frag)

 // Allocate new buffer to hold the full pieces and the fragment.
 buf := make([]byte, n)
 n = 0
 // Copy full pieces and fragment in.
 for i := range full {
  n += copy(buf[n:], full[i])
 }
 copy(buf[n:], frag)
 return buf, err
}

總結(jié)

讀取文件中一行內(nèi)容時(shí),ReadSlice和ReadLine性能優(yōu)于ReadBytes和ReadString,但由于ReadLine對換行的處理更加全面(兼容\n和\r\n換行),因此,實(shí)際開發(fā)過程中,建議使用ReadLine函數(shù)。

 到此這篇關(guān)于Go語言文件讀取的一些總結(jié)的文章就介紹到這了,更多相關(guān)Go語言文件讀取內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang中defer的關(guān)鍵特性示例詳解

    golang中defer的關(guān)鍵特性示例詳解

    defer是golang語言中的關(guān)鍵字,用于資源的釋放,會在函數(shù)返回之前進(jìn)行調(diào)用。下面這篇文章主要給大家介紹了關(guān)于golang中defer的關(guān)鍵特性,文中通過示例代碼介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。
    2017-08-08
  • golang實(shí)現(xiàn)java uuid的序列化方法

    golang實(shí)現(xiàn)java uuid的序列化方法

    這篇文章主要介紹了golang實(shí)現(xiàn)java uuid的序列化方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Go pprof內(nèi)存指標(biāo)含義備忘錄及案例分析

    Go pprof內(nèi)存指標(biāo)含義備忘錄及案例分析

    這篇文章主要介紹了Go pprof內(nèi)存指標(biāo)含義備忘錄問題,小編特此把問題及案例分享到腳本之家平臺供大家學(xué)習(xí),需要的朋友可以參考下
    2020-03-03
  • golang引入自定義包的兩種方法

    golang引入自定義包的兩種方法

    本文主要介紹了golang引入自定義包的兩種方法,第一種是傳統(tǒng)的手動管理,第二種是使用go.mod文件,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-03-03
  • Golang import本地包和導(dǎo)入問題相關(guān)詳解

    Golang import本地包和導(dǎo)入問題相關(guān)詳解

    這篇文章主要介紹了Golang import本地包和導(dǎo)入問題相關(guān)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • Go遞歸修改文件名的實(shí)例代碼

    Go遞歸修改文件名的實(shí)例代碼

    這篇文章主要介紹了Go遞歸修改文件名的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的餓參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • go:垃圾回收GC觸發(fā)條件詳解

    go:垃圾回收GC觸發(fā)條件詳解

    這篇文章主要介紹了go:垃圾回收GC觸發(fā)條件詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • 通過手機(jī)案例理解Go設(shè)計(jì)模式之裝飾器模式的功能屬性

    通過手機(jī)案例理解Go設(shè)計(jì)模式之裝飾器模式的功能屬性

    這篇文章主要為大家介紹了Go設(shè)計(jì)模式之裝飾器模式的功能屬性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • go字符串拼接方式及性能比拼小結(jié)

    go字符串拼接方式及性能比拼小結(jié)

    在golang中字符串的拼接方式有多種,本文將會介紹比較常用的幾種方式,并且對各種方式進(jìn)行壓測,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • GO語言中defer實(shí)現(xiàn)原理的示例詳解

    GO語言中defer實(shí)現(xiàn)原理的示例詳解

    這篇文章主要為大家詳細(xì)介紹了Go語言中defer實(shí)現(xiàn)原理的相關(guān)資料,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Go語言有一定的幫助,需要的可以參考一下
    2023-02-02

最新評論

沁阳市| 石屏县| 且末县| 什邡市| 武安市| 淳安县| 武定县| 右玉县| 那曲县| 纳雍县| 竹山县| 张掖市| 横山县| 石嘴山市| 石台县| 长岭县| 抚顺市| 宣城市| 景洪市| 咸丰县| 荆州市| 定西市| 介休市| 许昌市| 通海县| 玉树县| 运城市| 郴州市| 金坛市| 仙居县| 扬中市| 枣阳市| 灌云县| 临洮县| 衡阳市| 昔阳县| 蓝山县| 临夏县| 县级市| 鹤岗市| 浦城县|