欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言使用ioutil.ReadAll函數(shù)需要注意基本說明

 更新時(shí)間:2023年07月12日 09:58:25   作者:陳晨  
這篇文章主要為大家介紹了Go語言使用ioutil.ReadAll函數(shù)需要注意基本說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

1. 引言

當(dāng)我們需要將數(shù)據(jù)一次性加載到內(nèi)存中,ioutil.ReadAll 函數(shù)是一個(gè)方便的選擇,但是ioutil.ReadAll 的使用是需要注意的。

在這篇文章中,我們將首先對(duì)ioutil.ReadAll函數(shù)進(jìn)行基本介紹,之后會(huì)介紹其存在的問題,以及引起該問題的原因,最后給出了ioutil.ReadAll 函數(shù)的替代操作。通過這些內(nèi)容,希望能幫助你更好地理解和使用ioutil.ReadAll 函數(shù)。

2. 基本說明

ioutil.ReadAll其實(shí)是標(biāo)準(zhǔn)庫的一個(gè)函數(shù),其作用是從Reader 參數(shù)讀取所有的數(shù)據(jù),直到遇到EOF為止,函數(shù)定義如下:

func ReadAll(r io.Reader) ([]byte, error)

其中r 為待讀取數(shù)據(jù)的Reader,數(shù)據(jù)讀取結(jié)果將以字節(jié)切片的形式來返回,如果讀取過程中遇到了錯(cuò)誤,也會(huì)返回對(duì)應(yīng)的錯(cuò)誤。

下面通過一個(gè)簡(jiǎn)單的示例,來簡(jiǎn)單說明ioutil.ReadAll 函數(shù)的使用:

package main
import (
        "fmt"
        "io/ioutil"
        "os"
)
func main() {
        filePath := "example.txt"
        // 打開文件
        file, err := os.Open(filePath)
        if err != nil {
              fmt.Println("無法打開文件:%s", err)
              return
        }
        defer file.Close()
        // 讀取文件全部數(shù)據(jù)
        data, err := ioutil.ReadAll(file)
        if err != nil {
                fmt.Println("無法讀取文件:%s", err)
                return
        }
        // 將讀取到的數(shù)據(jù)轉(zhuǎn)換為字符串并輸出
        content := string(data)
        fmt.Println("文件內(nèi)容:")
        fmt.Println(content)
}

在這個(gè)示例中,我們使用os.Open 函數(shù)打開指定路徑的文件,獲取到一個(gè)os.File 對(duì)象,接著,調(diào)用 ioutil.ReadAll 便能讀取到文件的全部數(shù)據(jù)。

3. 為什么使用 ioutil.ReadAll 需要注意

從上面的基本說明我們可以得知,ioutil.ReadAll 的作用是讀取指定數(shù)據(jù)源的全部數(shù)據(jù),并將其以字節(jié)數(shù)組的形式來返回。比如,我們想要將整個(gè)文件的數(shù)據(jù)加載到內(nèi)存中,此時(shí)就可以使用 ioutil.ReadAll 函數(shù)來實(shí)現(xiàn)。

那這里就有一個(gè)問題, 加載一份數(shù)據(jù)到內(nèi)存中,會(huì)耗費(fèi)多少內(nèi)存資源呢? 按照我們的理解,正常是數(shù)據(jù)源數(shù)據(jù)有多大,就大概消耗多大的內(nèi)存資源。

然而,如果使用 ioutil.ReadAll 函數(shù)加載數(shù)據(jù)時(shí)消耗的內(nèi)存資源,可能與我們的想法存在一些差距。通常使用 ioutil.ReadAll 函數(shù)加載全部數(shù)據(jù)有可能會(huì)消耗更多的內(nèi)存。

下面我們創(chuàng)建一個(gè)10M的文件,然后寫一個(gè)基準(zhǔn)測(cè)試函數(shù),來展示使用 ioutil.ReadAll 加載整個(gè)文件的數(shù)據(jù),需要分配多少內(nèi)存,函數(shù)如下:

func BenchmarkReadAllMemoryUsage(b *testing.B) {
   filePath := "largefile.txt"
   for n := 0; n < b.N; n++ {
      // 打開文件
      file, err := os.Open(filePath)
      if err != nil {
         fmt.Println("無法打開文件:%r", err)
         return
      }
      defer file.Close()
      _, err = ioutil.ReadAll(file)
      if err != nil {
         b.Fatal(err)
      }
   }
}

基準(zhǔn)測(cè)試的運(yùn)行結(jié)果如下:

BenchmarkReadAllMemoryUsage-4                106          14385391 ns/op        52263424 B/op         42 allocs/op

其中106,表示基準(zhǔn)測(cè)試的迭代次數(shù),14385391 ns/op, 表示每次迭代的平均執(zhí)行時(shí)間,52263424 B/op表示每次迭代的平均內(nèi)存分配量,42 allocs/op 表示每次迭代的平均分配次數(shù),

上面基準(zhǔn)測(cè)試的結(jié)果,我們主要關(guān)注每次迭代需要消耗的內(nèi)存量,也就是 52263424 B/op 這個(gè)數(shù)據(jù),這個(gè)大概相當(dāng)于50M左右。在這個(gè)示例中,我們使用 ioutil.ReadAll 加載一個(gè)10M大小的文件,此時(shí)需要分配50M的內(nèi)存,是文件大小的5倍。

從這里我們可以看出,使用ioutil.ReadAll 加載數(shù)據(jù)時(shí),存在的一個(gè)注意點(diǎn),便是其分配的內(nèi)存遠(yuǎn)遠(yuǎn)大于待加載數(shù)據(jù)的大小。

那我們就有疑問了,為什么 ioutil.ReadAll 加載數(shù)據(jù)時(shí),會(huì)消耗這么多內(nèi)存呢? 下面我們通過說明ioutil.ReadAll 函數(shù)的實(shí)現(xiàn),來解釋其中的原因。

4. 為什么這么消耗內(nèi)存

ioutil.ReadAll 函數(shù)的實(shí)現(xiàn)其實(shí)比較簡(jiǎn)單,ReadAll 函數(shù)會(huì)初始化一個(gè)字節(jié)切片緩沖區(qū),然后調(diào)用源Reader 的Read 方法不斷讀取數(shù)據(jù),直接讀取到EOF 為止。

不過需要注意的是,ReadAll 函數(shù)初始化的緩沖區(qū),其初始化大小只有512個(gè)字節(jié),在讀取過程中,如果緩沖區(qū)長(zhǎng)度不夠,將會(huì)不斷擴(kuò)容該緩沖區(qū),直到緩沖區(qū)能夠容納所有待讀取數(shù)據(jù)為止。所以調(diào)用ioutil.ReadAll 可能會(huì)存在多次內(nèi)存分配的現(xiàn)象。下面我們來看其代碼實(shí)現(xiàn):

func ReadAll(r Reader) ([]byte, error) {
   // 初始化一個(gè) 512 個(gè)字節(jié)長(zhǎng)度的 字節(jié)切片
   b := make([]byte, 0, 512)
   for {
      // len(b) == cap(b),此時(shí)緩沖區(qū)已滿,需要擴(kuò)容
      if len(b) == cap(b) {
         // 首先append(b,0), 觸發(fā)切片的擴(kuò)容機(jī)制
         // 然后再去掉前面 append 的 '0' 字符
         b = append(b, 0)[:len(b)]
      }
      // 調(diào)用Read 方法讀取數(shù)據(jù)
      n, err := r.Read(b[len(b):cap(b)])
      // 更新切片 len 字段的值
      b = b[:len(b)+n]
      if err != nil {
         // 讀取到 EOF, 此時(shí)直接返回
         if err == EOF {
            err = nil
         }
         return b, err
      }
   }
}

從上面代碼實(shí)現(xiàn)來看,使用 ioutil.ReadAll 加載數(shù)據(jù)需要分配大量?jī)?nèi)存的原因是因?yàn)榍衅牟粩鄶U(kuò)容導(dǎo)致的。

ioutil.ReadAll 加載數(shù)據(jù)時(shí),一開始只初始化了一個(gè)512字節(jié)大小的切片,如果待加載的數(shù)據(jù)超過512字節(jié)的話,切片會(huì)觸發(fā)擴(kuò)容操作。同時(shí)其也不是一次性擴(kuò)容到能夠容納所有數(shù)據(jù)的長(zhǎng)度,而是基于切片的擴(kuò)容機(jī)制來決定的。接下來可能會(huì)擴(kuò)容到1024個(gè)字節(jié),會(huì)重新申請(qǐng)一塊內(nèi)存空間,然后將原切片數(shù)據(jù)拷貝過去。

之后如果數(shù)據(jù)超過1024個(gè)字節(jié),切片會(huì)繼續(xù)擴(kuò)容的操作,如此反復(fù),直到切片能夠容納所有的數(shù)據(jù)為止,這個(gè)過程中會(huì)存在多次的內(nèi)存分配的操作,導(dǎo)致大量?jī)?nèi)存的消耗。

因此,當(dāng)使用 ioutil.ReadAll加載數(shù)據(jù)時(shí),內(nèi)存消耗會(huì)隨著數(shù)據(jù)的大小而增加。特別是在處理大文件或大數(shù)據(jù)集時(shí),可能需要分配大量的內(nèi)存空間。這就解釋了為什么僅加載一個(gè)10M大小的文件,就需要分配50M內(nèi)存的現(xiàn)象。

5. 替換操作

既然 ioutil.ReadAll 這么消耗內(nèi)存,那么我們應(yīng)該盡量避免對(duì)其進(jìn)行使用。但是有時(shí)候,我們又需要讀取全部數(shù)據(jù)到內(nèi)存中,這個(gè)時(shí)候其實(shí)可以使用其他函數(shù)來替代ioutil.ReadAll。下面從文件讀取和網(wǎng)絡(luò)IO讀取這兩個(gè)方面來進(jìn)行介紹。

5.1 文件讀取

ioutil 工具包中,還存在一個(gè)ReadFile的工具函數(shù),能夠加載文件的全部數(shù)據(jù)到內(nèi)存中,函數(shù)定義如下:

func ReadFile(filename string) ([]byte, error) {}

ReadFile函數(shù)的使用非常簡(jiǎn)單,只需要傳入一個(gè)待加載文件的路徑,返回的數(shù)據(jù)為文件的內(nèi)容。下面通過一個(gè)基準(zhǔn)函數(shù),展示其加載文件時(shí)需要的分配內(nèi)存數(shù)等的數(shù)據(jù),來和ioutil.ReadAll做一個(gè)比較:

func BenchmarkReadFileMemoryUsage(b *testing.B) {
   filePath := "largefile.txt"
   for n := 0; n &lt; b.N; n++ {
      _, err := ioutil.ReadFile(filePath)
      if err != nil {
         b.Fatal(err)
      }
   }
}

上面基準(zhǔn)測(cè)試運(yùn)行結(jié)果如下:

// ReadFile 函數(shù)基準(zhǔn)測(cè)試結(jié)果
BenchmarkReadFileMemoryUsage-4                592           1942212 ns/op        10494290 B/op          5 allocs/op
// ReadAll 函數(shù)基準(zhǔn)測(cè)試結(jié)果
BenchmarkReadAllMemoryUsage-4                106          14385391 ns/op        52263424 B/op         42 allocs/op

使用ReadFile加載整個(gè)文件的數(shù)據(jù),分配的內(nèi)存數(shù)大概也為10M左右,同時(shí)執(zhí)行時(shí)間和內(nèi)存分配次數(shù),也相對(duì)于ReadAll 函數(shù)來看,也相對(duì)更小。

因此,如果我們確實(shí)需要加載文件的全部數(shù)據(jù),此時(shí)使用ReadFile相對(duì)于ReadAll 肯定是更為合適的。

5.2 網(wǎng)絡(luò)IO讀取

如果是網(wǎng)絡(luò)IO操作,此時(shí)我們需要假定一個(gè)前提,是所有的響應(yīng)數(shù)據(jù),應(yīng)該都是有響應(yīng)頭的,能夠通過響應(yīng)頭,獲取到響應(yīng)體的長(zhǎng)度,然后再基于此讀取全部響應(yīng)體的數(shù)據(jù)。

這里可以使用io.Copy函數(shù)來將數(shù)據(jù)拷貝,從而來替代ioutil.ReadAll,下面是一個(gè)大概代碼結(jié)構(gòu):

package main
import (
        "bytes"
        "fmt"
        "io"
        "os"
)
func main() {
        // 1. 建立一個(gè)網(wǎng)絡(luò)連接
        src := xxx
        defer src.Close()
        // 2. 讀取報(bào)文頭,獲取請(qǐng)求包的長(zhǎng)度
        size := xxx
        // 3. 基于該 size 創(chuàng)建一個(gè) 字節(jié)切片
        buf := make([]byte, size)
        buffer := bytes.NewBuffer(buf)
        // 4. 使用buffer來讀取數(shù)據(jù)
        _, err = io.Copy(&buffer, srcFile)
        if err != nil {
                fmt.Println("Failed to copy data:", err)
                return
        }
        // 現(xiàn)在數(shù)據(jù)已加載到內(nèi)存中的緩沖區(qū)(buffer)中
        fmt.Println("Data loaded into buffer successfully.")
}

通過這種方式,能夠使用io.Copy 函數(shù)替換ioutil.ReadAll ,讀取到所有的數(shù)據(jù),而io.Copy 函數(shù)不會(huì)存在 ioutil.ReadAll 函數(shù)存在的問題。

6. 總結(jié)

本文首先對(duì) ioutil.ReadAll 進(jìn)行了基本的說明,同時(shí)給了一個(gè)簡(jiǎn)單的使用示例。

隨后,通過基準(zhǔn)測(cè)試展示了使用 ioutil.ReadAll 加載數(shù)據(jù),消耗的內(nèi)存可能遠(yuǎn)遠(yuǎn)大于待加載的數(shù)據(jù)。之后,通過對(duì)源碼講解,說明了導(dǎo)致這個(gè)現(xiàn)象導(dǎo)致的原因。

最后,給出了一些替代方案,如使用 ioutil.ReadFile 函數(shù)和使用 io.Copy 函數(shù)等,以減少內(nèi)存占用?;谝陨蟽?nèi)容,便完成了對(duì)ioutil.ReadAll 函數(shù)的介紹,希望對(duì)你有所幫助。

更多關(guān)于go ioutil.ReadAll函數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang實(shí)現(xiàn)http重定向https

    Golang實(shí)現(xiàn)http重定向https

    這篇文章介紹了Golang實(shí)現(xiàn)http重定向https的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • Go接口構(gòu)建可擴(kuò)展Go應(yīng)用示例詳解

    Go接口構(gòu)建可擴(kuò)展Go應(yīng)用示例詳解

    本文深入探討了Go語言中接口的概念和實(shí)際應(yīng)用場(chǎng)景。從基礎(chǔ)知識(shí)如接口的定義和實(shí)現(xiàn),到更復(fù)雜的實(shí)戰(zhàn)應(yīng)用如解耦與抽象、多態(tài)、錯(cuò)誤處理、插件架構(gòu)以及資源管理,文章通過豐富的代碼示例和詳細(xì)的解釋,展示了Go接口在軟件開發(fā)中的強(qiáng)大功能和靈活性
    2023-10-10
  • Golang?鎖原理的簡(jiǎn)單實(shí)現(xiàn)

    Golang?鎖原理的簡(jiǎn)單實(shí)現(xiàn)

    本文主要介紹了Golang?鎖原理的簡(jiǎn)單實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • 詳解Golang的GC和內(nèi)存逃逸

    詳解Golang的GC和內(nèi)存逃逸

    這篇文章主要給大家詳細(xì)介紹了Golang的GC和內(nèi)存逃逸,文章中有詳細(xì)的代碼示例,對(duì)我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-07-07
  • go的websocket實(shí)現(xiàn)原理與用法詳解

    go的websocket實(shí)現(xiàn)原理與用法詳解

    這篇文章主要介紹了go的websocket實(shí)現(xiàn)原理與用法,詳細(xì)分析了websocket的功能、原理及Go語言實(shí)現(xiàn)websocket的相關(guān)技巧,需要的朋友可以參考下
    2016-07-07
  • go語言制作一個(gè)gif動(dòng)態(tài)圖

    go語言制作一個(gè)gif動(dòng)態(tài)圖

    這篇文章主要介紹了go制作一個(gè)gif動(dòng)態(tài)圖的相關(guān)資料,需要的朋友可以參考下
    2015-03-03
  • 簡(jiǎn)單聊聊Go語言中空結(jié)構(gòu)體和空字符串的特殊之處

    簡(jiǎn)單聊聊Go語言中空結(jié)構(gòu)體和空字符串的特殊之處

    在日常的編程過程中,大家應(yīng)該經(jīng)常能遇到各種”空“吧,比如空指針、空結(jié)構(gòu)體、空字符串等,本文就以?Go?語言為例,一起來看看空結(jié)構(gòu)體和空字符串在?Go?語言中的特殊之處吧
    2024-03-03
  • Golang map實(shí)現(xiàn)原理淺析

    Golang map實(shí)現(xiàn)原理淺析

    Go中Map是一個(gè)KV對(duì)集合,下面這篇文章主要給大家介紹了關(guān)于Golang中map探究的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-12-12
  • Go設(shè)計(jì)模式之狀態(tài)模式圖文詳解

    Go設(shè)計(jì)模式之狀態(tài)模式圖文詳解

    狀態(tài)模式是一種行為設(shè)計(jì)模式, 讓你能在一個(gè)對(duì)象的內(nèi)部狀態(tài)變化時(shí)改變其行為, 使其看上去就像改變了自身所屬的類一樣,本文將通過一些圖片來給大家詳細(xì)的介紹一下Go的狀態(tài)模式,需要的朋友可以參考下
    2023-08-08
  • 使用golang編寫一個(gè)并發(fā)工作隊(duì)列

    使用golang編寫一個(gè)并發(fā)工作隊(duì)列

    這篇文章主要介紹了使用golang編寫一個(gè)并發(fā)工作隊(duì)列的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05

最新評(píng)論