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

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è)簡單的示例,來簡單說明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í)比較簡單,ReadAll 函數(shù)會(huì)初始化一個(gè)字節(jié)切片緩沖區(qū),然后調(diào)用源Reader 的Read 方法不斷讀取數(shù)據(jù),直接讀取到EOF 為止。

不過需要注意的是,ReadAll 函數(shù)初始化的緩沖區(qū),其初始化大小只有512個(gè)字節(jié),在讀取過程中,如果緩沖區(qū)長度不夠,將會(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é)長度的 字節(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ù)需要分配大量內(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ù)的長度,而是基于切片的擴(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)致大量內(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ù)的使用非常簡單,只需要傳入一個(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)體的長度,然后再基于此讀取全部響應(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)求包的長度
        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è)簡單的使用示例。

隨后,通過基準(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 中的可測(cè)試示例函數(shù)(Example Function)詳解

    Golang 中的可測(cè)試示例函數(shù)(Example Function)詳解

    這篇文章詳細(xì)講解了 Golang 中的可測(cè)試示例函數(shù),示例函數(shù)類似于單元測(cè)試函數(shù),但沒有  *testing 類型的參數(shù),編寫示例函數(shù)也是很容易的,本文就通過代碼示例給大家介紹一下Golang的可測(cè)試示例函數(shù),需要的朋友可以參考下
    2023-07-07
  • Golang?sync.Map底層實(shí)現(xiàn)場景示例詳解

    Golang?sync.Map底層實(shí)現(xiàn)場景示例詳解

    這篇文章主要為大家介紹了Golang?sync.Map底層實(shí)現(xiàn)及使用場景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Golang讀寫二進(jìn)制文件方法總結(jié)

    Golang讀寫二進(jìn)制文件方法總結(jié)

    使用?Golang?的?encoding/gob?包讀寫二進(jìn)制文件非常方便,而且代碼量也非常少,本文就來通過兩個(gè)示例帶大家了解一下encoding/gob的具體用法吧
    2023-05-05
  • 如何解析golang中Context在HTTP服務(wù)中的角色

    如何解析golang中Context在HTTP服務(wù)中的角色

    這篇文章主要介紹了如何解析golang中Context在HTTP服務(wù)中的角色問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • go語言實(shí)現(xiàn)十大常見的排序算法示例

    go語言實(shí)現(xiàn)十大常見的排序算法示例

    這篇文章主要為大家介紹了go語言實(shí)現(xiàn)十大常見的排序算法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • Go語言中多重賦值里的空白標(biāo)識(shí)符的使用

    Go語言中多重賦值里的空白標(biāo)識(shí)符的使用

    Go語言中,空白標(biāo)識(shí)符_用于多重賦值時(shí)忽略多余值,提升代碼簡潔性,適用于函數(shù)返回、遍歷映射、接口斷言、數(shù)據(jù)庫查詢及通道接收等場景,下面就來介紹一下,感興趣的可以了解一下
    2025-06-06
  • Golang中常見的三種并發(fā)控制方式使用小結(jié)

    Golang中常見的三種并發(fā)控制方式使用小結(jié)

    這篇文章主要為大家詳細(xì)介紹了如何對(duì)goroutine并發(fā)行為的控制,在Go中最常見的有三種方式:sync.WaitGroup、channel和Context,下面我們就來看看他們的具體使用吧
    2024-01-01
  • go語言實(shí)現(xiàn)依賴注入的示例代碼

    go語言實(shí)現(xiàn)依賴注入的示例代碼

    依賴注入和控制反轉(zhuǎn)恰恰相反,它是一種具體的編碼技巧,我們不通過 new 的方式在類內(nèi)部創(chuàng)建依賴類的對(duì)象,而是將依賴的類對(duì)象在外部創(chuàng)建好之后,通過構(gòu)造函數(shù)、函數(shù)參數(shù)等方式傳遞給類來使用,本文將給大家介紹go語言實(shí)現(xiàn)依賴注入,需要的朋友可以參考下
    2024-01-01
  • 詳解如何通過Go來操作Redis實(shí)現(xiàn)簡單的讀寫操作

    詳解如何通過Go來操作Redis實(shí)現(xiàn)簡單的讀寫操作

    作為最常用的分布式緩存中間件——Redis,了解運(yùn)作原理和如何使用是十分有必要的,今天來學(xué)習(xí)如何通過Go來操作Redis實(shí)現(xiàn)基本的讀寫操作,需要的朋友可以參考下
    2023-09-09
  • golang中context的作用詳解

    golang中context的作用詳解

    這篇文章主要介紹了golang中context的作用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01

最新評(píng)論

六安市| 临湘市| 吉隆县| 旌德县| 沅陵县| 永城市| 郓城县| 如皋市| 饶河县| 梨树县| 额敏县| 长子县| 富川| 若羌县| 河津市| 阿瓦提县| 靖宇县| 大关县| 沅陵县| 汾阳市| 丽江市| 富阳市| 庆城县| 天柱县| 万安县| 广河县| 将乐县| 汾西县| 连平县| 宣武区| 江西省| 平邑县| 陇川县| 柘荣县| 龙州县| 鲜城| 宜州市| 唐海县| 奉化市| 郴州市| 娱乐|