国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

為什么要避免在 Go 中使用 ioutil.ReadAll?

開發 前端
本篇文章從一個問題入手,重點研究了 ioutil.ReadAll 函數。主要原因是在小數據量的情況下,這個函數并沒有什么問題,但當數據量大時,它就變成了一顆定時炸彈。有可能會影響程序的性能,甚至會導致程序崩潰。

ioutil.ReadAll 主要的作用是從一個 io.Reader 中讀取所有數據,直到結尾。

在 GitHub 上搜索 ioutil.ReadAll,類型選擇 Code,語言選擇 Go,一共得到了 637307 條結果。

這說明 ioutil.ReadAll 還是挺受歡迎的,主要也是用起來確實方便。

但是當遇到大文件時,這個函數就會暴露出兩個明顯的缺點:

性能問題,文件越大,性能越差。

文件過大的話,可能直接撐爆內存,導致程序崩潰。

為什么會這樣呢?這篇文章就通過源碼來分析背后的原因,并試圖給出更好的解決方案。

下面我們正式開始。

ioutil.ReadAll

首先,我們通過一個例子看一下 ioutil.ReadAll 的使用場景。比如說,使用 http.Client 發送 GET 請求,然后再讀取返回內容:

  1. func main() { 
  2.     res, err := http.Get("http://www.google.com/robots.txt"
  3.     if err != nil { 
  4.         log.Fatal(err) 
  5.     } 
  6.  
  7.     robots, err := io.ReadAll(res.Body) 
  8.     res.Body.Close() 
  9.     if err != nil { 
  10.         log.Fatal(err) 
  11.     } 
  12.     fmt.Printf("%s", robots) 

http.Get() 返回的數據,存儲在 res.Body 中,通過 ioutil.ReadAll 將其讀取出來。

表面上看這段代碼沒有什么問題,但仔細分析卻并非如此。想要探究其背后的原因,就只能靠源碼說話。

ioutil.ReadAll 的源碼如下:

  1. // src/io/ioutil/ioutil.go 
  2.  
  3. func ReadAll(r io.Reader) ([]byte, error) { 
  4.     return io.ReadAll(r) 

Go 1.16 版本開始,直接調用 io.ReadAll() 函數,下面再看看 io.ReadAll() 的實現:

  1. // src/io/io.go 
  2.  
  3. func ReadAll(r Reader) ([]byte, error) { 
  4.     // 創建一個 512 字節的 buf 
  5.     b := make([]byte, 0, 512) 
  6.     for { 
  7.         if len(b) == cap(b) { 
  8.             // 如果 buf 滿了,則追加一個元素,使其重新分配內存 
  9.             b = append(b, 0)[:len(b)] 
  10.         } 
  11.         // 讀取內容到 buf 
  12.         n, err := r.Read(b[len(b):cap(b)]) 
  13.         b = b[:len(b)+n] 
  14.         // 遇到結尾或者報錯則返回 
  15.         if err != nil { 
  16.             if err == EOF { 
  17.                 err = nil 
  18.             } 
  19.             return b, err 
  20.         } 
  21.     } 

我給代碼加上了必要的注釋,這段代碼的執行主要分三個步驟:

  • 創建一個 512 字節的 buf;
  • 不斷讀取內容到 buf,當 buf 滿的時候,會追加一個元素,促使其重新分配內存;
  • 直到結尾或報錯,則返回;

知道了執行步驟,但想要分析其性能問題,還需要了解 Go 切片的擴容策略,如下:

  • 如果期望容量大于當前容量的兩倍就會使用期望容量;
  • 如果當前切片的長度小于 1024 就會將容量翻倍;
  • 如果當前切片的長度大于 1024 就會每次增加 25% 的容量,直到新容量大于期望容量;

也就是說,如果待拷貝數據的容量小于 512 字節的話,性能不受影響。但如果超過 512 字節,就會開始切片擴容。數據量越大,擴容越頻繁,性能受影響越大。

如果數據量足夠大的話,內存可能就直接撐爆了,這樣的話影響就大了。

那有更好的替換方案嗎?當然是有的,我們接著往下看。

io.Copy

可以使用 io.Copy 函數來代替,源碼定義如下:

  1. src/io/io.go 
  2.  
  3. func Copy(dst Writer, src Reader) (written int64, err error) { 
  4.     return copyBuffer(dst, src, nil) 

其功能是直接從 src 讀取數據,并寫入到 dst。

和 ioutil.ReadAll 最大的不同就是沒有把所有數據一次性都取出來,而是不斷讀取,不斷寫入。

具體實現 Copy 的邏輯在 copyBuffer 函數中實現:

  1. // src/io/io.go 
  2.  
  3. func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) { 
  4.     // 如果源實現了 WriteTo 方法,則直接調用 WriteTo 
  5.     if wt, ok := src.(WriterTo); ok { 
  6.         return wt.WriteTo(dst) 
  7.     } 
  8.     // 同樣的,如果目標實現了 ReaderFrom 方法,則直接調用 ReaderFrom 
  9.     if rt, ok := dst.(ReaderFrom); ok { 
  10.         return rt.ReadFrom(src) 
  11.     } 
  12.     // 如果 buf 為空,則創建 32KB 的 buf 
  13.     if buf == nil { 
  14.         size := 32 * 1024 
  15.         if l, ok := src.(*LimitedReader); ok && int64(size) > l.N { 
  16.             if l.N < 1 { 
  17.                 size = 1 
  18.             } else { 
  19.                 size = int(l.N) 
  20.             } 
  21.         } 
  22.         buf = make([]byte, size
  23.     } 
  24.     // 循環讀取數據并寫入 
  25.     for { 
  26.         nr, er := src.Read(buf) 
  27.         if nr > 0 { 
  28.             nw, ew := dst.Write(buf[0:nr]) 
  29.             if nw < 0 || nr < nw { 
  30.                 nw = 0 
  31.                 if ew == nil { 
  32.                     ew = errInvalidWrite 
  33.                 } 
  34.             } 
  35.             written += int64(nw) 
  36.             if ew != nil { 
  37.                 err = ew 
  38.                 break 
  39.             } 
  40.             if nr != nw { 
  41.                 err = ErrShortWrite 
  42.                 break 
  43.             } 
  44.         } 
  45.         if er != nil { 
  46.             if er != EOF { 
  47.                 err = er 
  48.             } 
  49.             break 
  50.         } 
  51.     } 
  52.     return written, err 

此函數執行步驟如下:

如果源實現了 WriteTo 方法,則直接調用 WriteTo 方法;

同樣的,如果目標實現了 ReaderFrom 方法,則直接調用 ReaderFrom 方法;

如果 buf 為空,則創建 32KB 的 buf;

最后就是循環 Read 和 Write;

對比之后就會發現,io.Copy 函數不會一次性讀取全部數據,也不會頻繁進行切片擴容,顯然在數據量大時是更好的選擇。

ioutil 其他函數

再看看 ioutil 包的其他函數:

  • func ReadDir(dirname string) ([]os.FileInfo, error)
  • func ReadFile(filename string) ([]byte, error)
  • func WriteFile(filename string, data []byte, perm os.FileMode) error
  • func TempFile(dir, prefix string) (f *os.File, err error)
  • func TempDir(dir, prefix string) (name string, err error)
  • func NopCloser(r io.Reader) io.ReadCloser

下面舉例詳細說明:

ReadDir

  1. // ReadDir 讀取指定目錄中的所有目錄和文件(不包括子目錄)。 
  2. // 返回讀取到的文件信息列表和遇到的錯誤,列表是經過排序的。 
  3. func ReadDir(dirname string) ([]os.FileInfo, error) 

舉例:

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "io/ioutil" 
  6.  
  7. func main() { 
  8.     dirName := "../" 
  9.     fileInfos, _ := ioutil.ReadDir(dirName) 
  10.     fmt.Println(len(fileInfos)) 
  11.     for i := 0; i < len(fileInfos); i++ { 
  12.         fmt.Printf("%T\n", fileInfos[i]) 
  13.         fmt.Println(i, fileInfos[i].Name(), fileInfos[i].IsDir()) 
  14.  
  15.     } 

ReadFile

  1. // ReadFile 讀取文件中的所有數據,返回讀取的數據和遇到的錯誤 
  2. // 如果讀取成功,則 err 返回 nil,而不是 EOF 
  3. func ReadFile(filename string) ([]byte, error) 

舉例:

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "io/ioutil" 
  6.     "os" 
  7.  
  8. func main() { 
  9.     data, err := ioutil.ReadFile("./test.txt"
  10.     if err != nil { 
  11.         fmt.Println("read error"
  12.         os.Exit(1) 
  13.     } 
  14.     fmt.Println(string(data)) 

WriteFile

  1. // WriteFile 向文件中寫入數據,寫入前會清空文件。 
  2. // 如果文件不存在,則會以指定的權限創建該文件。 
  3. // 返回遇到的錯誤。 
  4. func WriteFile(filename string, data []byte, perm os.FileMode) error 

舉例:

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "io/ioutil" 
  6.  
  7. func main() { 
  8.     fileName := "./text.txt" 
  9.     s := "Hello AlwaysBeta" 
  10.     err := ioutil.WriteFile(fileName, []byte(s), 0777) 
  11.     fmt.Println(err) 

TempFile

  1. // TempFile 在 dir 目錄中創建一個以 prefix 為前綴的臨時文件,并將其以讀 
  2. // 寫模式打開。返回創建的文件對象和遇到的錯誤。 
  3. // 如果 dir 為空,則在默認的臨時目錄中創建文件(參見 os.TempDir),多次 
  4. // 調用會創建不同的臨時文件,調用者可以通過 f.Name() 獲取文件的完整路徑。 
  5. // 調用本函數所創建的臨時文件,應該由調用者自己刪除。 
  6. func TempFile(dir, prefix string) (f *os.File, err error) 

舉例:

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "io/ioutil" 
  6.     "os" 
  7.  
  8. func main() { 
  9.     f, err := ioutil.TempFile("./""Test"
  10.     if err != nil { 
  11.         fmt.Println(err) 
  12.     } 
  13.     defer os.Remove(f.Name()) // 用完刪除 
  14.     fmt.Printf("%s\n", f.Name()) 

TempDir

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "io/ioutil" 
  6.     "os" 
  7.  
  8. func main() { 
  9.     dir, err := ioutil.TempDir("./""Test"
  10.     if err != nil { 
  11.         fmt.Println(err) 
  12.     } 
  13.     defer os.Remove(dir) // 用完刪除 
  14.     fmt.Printf("%s\n", dir) 

NopCloser

  1. // NopCloser 將 r 包裝為一個 ReadCloser 類型,但 Close 方法不做任何事情。 
  2. func NopCloser(r io.Reader) io.ReadCloser 

這個函數的使用場景是這樣的:

有時候我們需要傳遞一個 io.ReadCloser 的實例,而我們現在有一個 io.Reader 的實例,比如:strings.Reader。

這個時候 NopCloser 就派上用場了。它包裝一個 io.Reader,返回一個 io.ReadCloser,相應的 Close 方法啥也不做,只是返回 nil。

舉例:

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "io/ioutil" 
  6.     "reflect" 
  7.     "strings" 
  8.  
  9. func main() { 
  10.     //返回 *strings.Reader 
  11.     reader := strings.NewReader("Hello AlwaysBeta"
  12.     r := ioutil.NopCloser(reader) 
  13.     defer r.Close() 
  14.  
  15.     fmt.Println(reflect.TypeOf(reader)) 
  16.     data, _ := ioutil.ReadAll(reader) 
  17.     fmt.Println(string(data)) 

總結

ioutil 提供了幾個很實用的工具函數,背后實現邏輯也并不復雜。

本篇文章從一個問題入手,重點研究了 ioutil.ReadAll 函數。主要原因是在小數據量的情況下,這個函數并沒有什么問題,但當數據量大時,它就變成了一顆定時炸彈。有可能會影響程序的性能,甚至會導致程序崩潰。

接下來給出對應的解決方案,在數據量大的情況下,最好使用 io.Copy 函數。

文章最后繼續介紹了 ioutil 的其他幾個函數,并給出了程序示例。相關代碼都會上傳到 GitHub,需要的同學可以自行下載。

好了,本文就到這里吧。關注我,帶你通過問題讀 Go 源碼。

源碼地址:

https://github.com/yongxinz/gopher

 

責任編輯:武曉燕 來源: AlwaysBeta
相關推薦

2022-12-26 00:25:06

2021-04-26 09:33:46

Go Iota語言

2022-06-02 08:48:39

Go枚舉器Iota

2025-01-15 09:06:58

CSSRegEx前端

2023-11-27 19:39:46

Goprotobuf

2022-01-17 16:09:43

Go語言開發

2024-07-08 00:01:00

GPM模型調度器

2021-10-13 14:06:46

MySQLUtf8符號

2021-11-29 22:59:34

Go Dockertest集成

2023-10-28 16:22:21

Go接口

2023-01-11 08:06:42

對象賦值項目開發

2022-06-30 08:03:13

Prisma數據庫工具開源

2020-05-25 15:37:47

物聯網設備密碼物聯網安全

2020-06-22 07:18:21

Java語言開發

2020-02-24 09:38:05

PythonGo語言Linux

2013-07-29 14:50:43

API

2024-01-02 10:38:22

Go語言數組

2016-03-24 09:53:24

swiftguardios

2023-11-30 09:00:00

TypeScript開發

2015-05-25 15:31:56

C語言學習和使用 C 語言
點贊
收藏

51CTO技術棧公眾號

va天堂va亚洲va影视| 成人精品在线观看| 日韩夫妻性生活xx| 亚洲区一区二区三区| 综合婷婷亚洲小说| а_天堂中文在线| 国产精品久久久久免费a∨大胸 | 免费的av在线| 欧美做爰性生交视频| 国产精品福利网| 狠狠干一区二区| 一区二区免费在线观看| 国产成人无码精品久久久性色| 久久久av网站| 久久99久久精品| 日本一二三区在线视频| 中文字幕日韩精品一区二区| 色婷婷综合中文久久一本| 欧美天堂社区| 中文字幕第12页| 黄色一级大片免费| 欧美野外猛男的大粗鳮| 综合精品久久久| 污视频在线观看网站| 欧美激情小视频| 国产农村妇女精品一二区| 91原色影院| 欧美丰满一区二区免费视频| 欧美成人高清视频在线观看| 成人污网站在线观看| 深夜福利91大全| 国产精品亲子乱子伦xxxx裸| 中文字幕成人一区| 欧美一区二区视频在线| 久久综合九色99| 欧美自拍资源在线| 97xxxxx| 成人天堂av| 一卡二卡三卡亚洲| 大地资源网3页在线观看| 欧美人动性xxxxz0oz| 91麻豆精品国产综合久久久| 成人性生交大片免费看96| 成人精品电影| 三级亚洲高清视频| 麻豆91在线播放免费| 亚洲国产成人午夜在线一区| 欧美日韩激情视频| 欧美在线影院一区二区| 欧美亚男人的天堂| 婷婷综合另类小说色区| 日韩欧美国产中文字幕| 国产精品高清亚洲| 亚洲成av人片在线观看无码| 欧美日韩在线第一页| 欧美性生交片4| 777色狠狠一区二区三区| 在线观看一区不卡| 精品国产免费一区二区三区四区| 精品国产伦理网| 亚洲精品国产电影| 午夜精品久久久久久久99黑人| 精品国产拍在线观看| x99av成人免费| 欧美黑人xxxx| 色婷婷久久综合| 亚洲欧美日本免费| 国产精品永久免费在线| 亚洲美女淫视频| 一区三区在线欧| 濑亚美莉一二区在线视频| 日韩免费观看av| 婷婷国产在线综合| 亚洲久久久久| 日本不卡视频| 图片区小说区区亚洲五月| 亚洲风情亚aⅴ在线发布| 国产一区二区三区四区五区美女| 经典三级一区二区| 蜜臀久久99精品久久久酒店新书 | 福利在线视频网站| 亚洲精品成人自拍| 亚洲新中文字幕| 久久蜜桃一区二区| 一本久久青青| 九色网友自拍视频手机在线| 99电影在线观看| 日韩一区二区视频在线观看| 韩国精品久久久| 我要色综合中文字幕| 日韩av电影免费| 久久久久久久久一区二区| 亚洲视频欧洲视频| 国产精品久久久久aaaa| 亚洲精品小说| 国产传媒av在线| 色综合手机在线| 亚洲xxxx18| 日韩精品中文字幕在线播放| 国产欧美日韩中文久久| 91tv官网精品成人亚洲| 爆操欧美美女| 女人另类性混交zo| 91在线精品播放| 日韩精品免费一线在线观看| 国产欧美日韩综合精品一区二区| 91精品国偷自产在线电影 | 97在线视频一区| 欧美综合天天夜夜久久| 国产精品一区二区黑丝| 久久99国产成人小视频| av毛片在线| 色婷婷综合网站| 日本午夜精品一区二区| 久久成人18免费网站| 一本到三区不卡视频| 国产99久久久国产精品潘金| 久久成人av| 欧美激情20| 午夜3点看的视频| 亚洲在线不卡| 国产精品劲爆视频| 日韩av一区在线观看| 亚洲精品高清在线| 免费成人在线观看视频| 国产精品三级| 毛片无码国产| 黄色av网址在线免费观看| 国产欧美高清在线| 久久99蜜桃综合影院免费观看| 久久韩剧网电视剧| 精品1区2区3区| 中文字幕国产一区| 美腿丝袜亚洲色图| 成人一二三区| 91精品亚洲一区在线观看| 麻豆传媒视频在线观看| 国内av免费| 91成人在线观看喷潮教学| 久久人人爽爽人人爽人人片av| 久久久人成影片一区二区三区观看| 日韩精品一区国产麻豆| 亚洲va欧美va国产va天堂影院| jlzzjlzz亚洲日本少妇| 国产免费成人| 日韩中文在线电影| 爱高潮www亚洲精品| 成人影院大全| 免费的黄网站在线观看| 亚洲1024| 99热在线观看| 成人黄色av片| 亚洲欧美日韩精品在线| 国产经品一区二区| 欧洲s码亚洲m码精品一区| 色777狠狠综合秋免鲁丝| 精品嫩草影院久久| 欧美日韩在线播放三区四区| 亚洲地区一二三色| 国产精品福利一区| 91丨九色丨尤物| 韩国女主播成人在线| 蜜桃久久av| 亚洲欧洲日本一区二区三区| 99久久婷婷| 精品在线播放| 九色丨蝌蚪丨成人| 试看120秒一区二区三区| 欧美精品高清| 日韩激情电影| 17videosex性欧美| 99热国产在线| 91伦理视频在线观看| 伊人中文字幕在线| 美国成人av| 欧美vide| 在线的色视频| 五丁香在线视频| 在线影院av| 五丁香在线视频| 中文av在线播放| 色资源网站在线观看| 九七午夜视频| 制服黑丝国产在线| 22288色视频在线观看| 黄网在线播放| 导航福利在线| 在线免费日韩| 国产一级免费在线观看| 韩国中文免费在线视频| 日本免费不卡| 国产九色在线| 午夜不卡视频| 97影院秋霞午夜在线观看| 91在线中字| 亚洲色图第三页| 欧美理论在线观看| 亚洲欧美日韩国产一区二区| 在线视频亚洲欧美| 一级日本免费的|