<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

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

      原文鏈接: 為什么要避免在 Go 中使用 ioutil.ReadAll?

      ioutil.ReadAll 主要的作用是從一個(gè) io.Reader 中讀取所有數(shù)據(jù),直到結(jié)尾。

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

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

      但是當(dāng)遇到大文件時(shí),這個(gè)函數(shù)就會(huì)暴露出兩個(gè)明顯的缺點(diǎn):

      1. 性能問題,文件越大,性能越差。
      2. 文件過大的話,可能直接撐爆內(nèi)存,導(dǎo)致程序崩潰。

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

      下面我們正式開始。

      ioutil.ReadAll

      首先,我們通過一個(gè)例子看一下 ioutil.ReadAll 的使用場(chǎng)景。比如說,使用 http.Client 發(fā)送 GET 請(qǐng)求,然后再讀取返回內(nèi)容:

      func main() {
      	res, err := http.Get("http://www.google.com/robots.txt")
      	if err != nil {
      		log.Fatal(err)
      	}
      	
      	robots, err := io.ReadAll(res.Body)
      	res.Body.Close()
      	if err != nil {
      		log.Fatal(err)
      	}
      	fmt.Printf("%s", robots)
      }
      

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

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

      ioutil.ReadAll 的源碼如下:

      // src/io/ioutil/ioutil.go
      
      func ReadAll(r io.Reader) ([]byte, error) {
      	return io.ReadAll(r)
      }
      

      Go 1.16 版本開始,直接調(diào)用 io.ReadAll() 函數(shù),下面再看看 io.ReadAll() 的實(shí)現(xiàn):

      // src/io/io.go
      
      func ReadAll(r Reader) ([]byte, error) {
          // 創(chuàng)建一個(gè) 512 字節(jié)的 buf
      	b := make([]byte, 0, 512)
      	for {
      		if len(b) == cap(b) {
      			// 如果 buf 滿了,則追加一個(gè)元素,使其重新分配內(nèi)存
      			b = append(b, 0)[:len(b)]
      		}
      		// 讀取內(nèi)容到 buf
      		n, err := r.Read(b[len(b):cap(b)])
      		b = b[:len(b)+n]
      		// 遇到結(jié)尾或者報(bào)錯(cuò)則返回
      		if err != nil {
      			if err == EOF {
      				err = nil
      			}
      			return b, err
      		}
      	}
      }
      

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

      1. 創(chuàng)建一個(gè) 512 字節(jié)的 buf
      2. 不斷讀取內(nèi)容到 buf,當(dāng) buf 滿的時(shí)候,會(huì)追加一個(gè)元素,促使其重新分配內(nèi)存;
      3. 直到結(jié)尾或報(bào)錯(cuò),則返回;

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

      1. 如果期望容量大于當(dāng)前容量的兩倍就會(huì)使用期望容量;
      2. 如果當(dāng)前切片的長(zhǎng)度小于 1024 就會(huì)將容量翻倍;
      3. 如果當(dāng)前切片的長(zhǎng)度大于 1024 就會(huì)每次增加 25% 的容量,直到新容量大于期望容量;

      也就是說,如果待拷貝數(shù)據(jù)的容量小于 512 字節(jié)的話,性能不受影響。但如果超過 512 字節(jié),就會(huì)開始切片擴(kuò)容。數(shù)據(jù)量越大,擴(kuò)容越頻繁,性能受影響越大。

      如果數(shù)據(jù)量足夠大的話,內(nèi)存可能就直接撐爆了,這樣的話影響就大了。

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

      io.Copy

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

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

      其功能是直接從 src 讀取數(shù)據(jù),并寫入到 dst

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

      具體實(shí)現(xiàn) Copy 的邏輯在 copyBuffer 函數(shù)中實(shí)現(xiàn):

      // src/io/io.go
      
      func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
      	// 如果源實(shí)現(xiàn)了 WriteTo 方法,則直接調(diào)用 WriteTo
      	if wt, ok := src.(WriterTo); ok {
      		return wt.WriteTo(dst)
      	}
      	// 同樣的,如果目標(biāo)實(shí)現(xiàn)了 ReaderFrom 方法,則直接調(diào)用 ReaderFrom
      	if rt, ok := dst.(ReaderFrom); ok {
      		return rt.ReadFrom(src)
      	}
      	// 如果 buf 為空,則創(chuàng)建 32KB 的 buf
      	if buf == nil {
      		size := 32 * 1024
      		if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
      			if l.N < 1 {
      				size = 1
      			} else {
      				size = int(l.N)
      			}
      		}
      		buf = make([]byte, size)
      	}
      	// 循環(huán)讀取數(shù)據(jù)并寫入
      	for {
      		nr, er := src.Read(buf)
      		if nr > 0 {
      			nw, ew := dst.Write(buf[0:nr])
      			if nw < 0 || nr < nw {
      				nw = 0
      				if ew == nil {
      					ew = errInvalidWrite
      				}
      			}
      			written += int64(nw)
      			if ew != nil {
      				err = ew
      				break
      			}
      			if nr != nw {
      				err = ErrShortWrite
      				break
      			}
      		}
      		if er != nil {
      			if er != EOF {
      				err = er
      			}
      			break
      		}
      	}
      	return written, err
      }
      

      此函數(shù)執(zhí)行步驟如下:

      1. 如果源實(shí)現(xiàn)了 WriteTo 方法,則直接調(diào)用 WriteTo 方法;
      2. 同樣的,如果目標(biāo)實(shí)現(xiàn)了 ReaderFrom 方法,則直接調(diào)用 ReaderFrom 方法;
      3. 如果 buf 為空,則創(chuàng)建 32KB 的 buf
      4. 最后就是循環(huán) ReadWrite

      對(duì)比之后就會(huì)發(fā)現(xiàn),io.Copy 函數(shù)不會(huì)一次性讀取全部數(shù)據(jù),也不會(huì)頻繁進(jìn)行切片擴(kuò)容,顯然在數(shù)據(jù)量大時(shí)是更好的選擇。

      ioutil 其他函數(shù)

      再看看 ioutil 包的其他函數(shù):

      • 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

      下面舉例詳細(xì)說明:

      ReadDir

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

      舉例:

      package main
      
      import (
      	"fmt"
      	"io/ioutil"
      )
      
      func main() {
      	dirName := "../"
      	fileInfos, _ := ioutil.ReadDir(dirName)
      	fmt.Println(len(fileInfos))
      	for i := 0; i < len(fileInfos); i++ {
      		fmt.Printf("%T\n", fileInfos[i])
      		fmt.Println(i, fileInfos[i].Name(), fileInfos[i].IsDir())
      
      	}
      }
      

      ReadFile

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

      舉例:

      package main
      
      import (
      	"fmt"
      	"io/ioutil"
      	"os"
      )
      
      func main() {
      	data, err := ioutil.ReadFile("./test.txt")
      	if err != nil {
      		fmt.Println("read error")
      		os.Exit(1)
      	}
      	fmt.Println(string(data))
      }
      

      WriteFile

      // WriteFile 向文件中寫入數(shù)據(jù),寫入前會(huì)清空文件。
      // 如果文件不存在,則會(huì)以指定的權(quán)限創(chuàng)建該文件。
      // 返回遇到的錯(cuò)誤。
      func WriteFile(filename string, data []byte, perm os.FileMode) error
      

      舉例:

      package main
      
      import (
      	"fmt"
      	"io/ioutil"
      )
      
      func main() {
      	fileName := "./text.txt"
      	s := "Hello AlwaysBeta"
      	err := ioutil.WriteFile(fileName, []byte(s), 0777)
      	fmt.Println(err)
      }
      

      TempFile

      // TempFile 在 dir 目錄中創(chuàng)建一個(gè)以 prefix 為前綴的臨時(shí)文件,并將其以讀
      // 寫模式打開。返回創(chuàng)建的文件對(duì)象和遇到的錯(cuò)誤。
      // 如果 dir 為空,則在默認(rèn)的臨時(shí)目錄中創(chuàng)建文件(參見 os.TempDir),多次
      // 調(diào)用會(huì)創(chuàng)建不同的臨時(shí)文件,調(diào)用者可以通過 f.Name() 獲取文件的完整路徑。
      // 調(diào)用本函數(shù)所創(chuàng)建的臨時(shí)文件,應(yīng)該由調(diào)用者自己刪除。
      func TempFile(dir, prefix string) (f *os.File, err error)
      

      舉例:

      package main
      
      import (
      	"fmt"
      	"io/ioutil"
      	"os"
      )
      
      func main() {
      	f, err := ioutil.TempFile("./", "Test")
      	if err != nil {
      		fmt.Println(err)
      	}
      	defer os.Remove(f.Name()) // 用完刪除
      	fmt.Printf("%s\n", f.Name())
      }
      

      TempDir

      // TempDir 功能同 TempFile,只不過創(chuàng)建的是目錄,返回目錄的完整路徑。
      func TempDir(dir, prefix string) (name string, err error)
      

      舉例:

      package main
      
      import (
      	"fmt"
      	"io/ioutil"
      	"os"
      )
      
      func main() {
      	dir, err := ioutil.TempDir("./", "Test")
      	if err != nil {
      		fmt.Println(err)
      	}
      	defer os.Remove(dir) // 用完刪除
      	fmt.Printf("%s\n", dir)
      }
      

      NopCloser

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

      這個(gè)函數(shù)的使用場(chǎng)景是這樣的:

      有時(shí)候我們需要傳遞一個(gè) io.ReadCloser 的實(shí)例,而我們現(xiàn)在有一個(gè) io.Reader 的實(shí)例,比如:strings.Reader

      這個(gè)時(shí)候 NopCloser 就派上用場(chǎng)了。它包裝一個(gè) io.Reader,返回一個(gè) io.ReadCloser,相應(yīng)的 Close 方法啥也不做,只是返回 nil

      舉例:

      package main
      
      import (
      	"fmt"
      	"io/ioutil"
      	"reflect"
      	"strings"
      )
      
      func main() {
      	//返回 *strings.Reader
      	reader := strings.NewReader("Hello AlwaysBeta")
      	r := ioutil.NopCloser(reader)
      	defer r.Close()
      
      	fmt.Println(reflect.TypeOf(reader))
      	data, _ := ioutil.ReadAll(reader)
      	fmt.Println(string(data))
      }
      

      總結(jié)

      ioutil 提供了幾個(gè)很實(shí)用的工具函數(shù),背后實(shí)現(xiàn)邏輯也并不復(fù)雜。

      本篇文章從一個(gè)問題入手,重點(diǎn)研究了 ioutil.ReadAll 函數(shù)。主要原因是在小數(shù)據(jù)量的情況下,這個(gè)函數(shù)并沒有什么問題,但當(dāng)數(shù)據(jù)量大時(shí),它就變成了一顆定時(shí)炸彈。有可能會(huì)影響程序的性能,甚至?xí)?dǎo)致程序崩潰。

      接下來給出對(duì)應(yīng)的解決方案,在數(shù)據(jù)量大的情況下,最好使用 io.Copy 函數(shù)。

      文章最后繼續(xù)介紹了 ioutil 的其他幾個(gè)函數(shù),并給出了程序示例。相關(guān)代碼都會(huì)上傳到 GitHub,需要的同學(xué)可以自行下載。

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


      源碼地址:

      推薦閱讀:

      參考文章:

      posted @ 2022-01-06 14:45  yongxinz  閱讀(1099)  評(píng)論(2)    收藏  舉報(bào)
      主站蜘蛛池模板: 蜜臀av一区二区三区日韩| 色丁香一区二区黑人巨大| 三男一女吃奶添下面视频| 亚洲区一区二区三区精品| 久久99精品久久久久久9| 福利一区二区不卡国产| 国产精品户外野外| 亚洲日韩精品一区二区三区无码 | 亚洲人成网站在线播放2019| 国内永久福利在线视频图片| 中文字幕精品亚洲字幕成| 熟妇人妻av中文字幕老熟妇| 成人午夜在线观看刺激| 377人体粉嫩噜噜噜| av亚洲一区二区在线| 国产一区二区三区在线观看免费 | 国产精品国三级国产av| 少妇人妻偷人偷人精品| 日韩免费无码视频一区二区三区 | 免费无码又爽又刺激高潮的app| 国内精品自在拍精选| 欧美性猛交xxxx乱大交极品| 亚洲精品中文字幕码专区| 芮城县| 在线播放国产精品一品道| 综合久久婷婷综合久久| 99精品人妻少妇一区二区| 加勒比久久综合网天天| 少妇扒开双腿自慰出白浆| 国产麻豆放荡av激情演绎| 天天爽夜夜爱| 国产美女自卫慰黄网站| 免费无码影视在线观看mov| 中文字幕国产在线精品| 亚洲国产午夜精品福利| 在线视频中文字幕二区| 国产色视频网站免费| 成人免费无遮挡在线播放| 国产呻吟久久久久久久92| 激情综合色区网激情五月| 国产亚洲国产精品二区|