前言:在 Golang 中函式之間傳遞變數時總是以值的方式傳遞的,無論是 int,string,bool,array 這樣的內置型別(或者說原始的型別),還是 slice,channel,map 這樣的參考型別,在函式間傳遞變數時,都是以值的方式傳遞,也就是說傳遞的都是值的副本,
在使用ioutil的ReadAll方法時查看了其內部實作如下,這讓我很痛苦,不明白為什么要這樣寫,下面我們就來一探究竟,
func ReadAll(r Reader) ([]byte, error) {
b := make([]byte, 0, 512)
for {
if len(b) == cap(b) {
// Add more capacity (let append pick how much).
b = append(b, 0)[:len(b)]
}
n, err := r.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
if err != nil {
if err == EOF {
err = nil
}
return b, err
}
}
}
討論這個問題之前先看一下標準庫中切片的內部結構
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
由切片的結構定義可知,切片的結構由三個資訊組成:
- 指標Data,指向底層陣列中切片指定的開始位置
- 長度Len,即切片的長度
- 容量Cap,也就是最大長度,即切片開始位置到陣列的最后位置的長度
最開始我想將一個檔案內容讀取到記憶體,我想到的操作是這樣的
func f1() {
f, _ := os.Open("F:\\hello.txt")
b := make([]byte, 0, 512)
read, err := f.Read(b)
if err != nil {
return
}
fmt.Println(read)//列印0
fmt.Println(b)//列印[]
}
為什么會出現這種情況呢?我們點開f.Read方法看到 Read reads up to len(b) bytes from the File.
讀取len(b) 長度byte的資料到b,那現在len(b)=0就一個位元組都不會讀取了,這時候你就會明白為什么上面標準庫中ReadAll引數為什么要用b[len(b):cap(b)]
(對切片的任何操作都會復制一個切片b[len(b):cap(b)]
操作對b切片結構體進行了復制,產生了新的切片并且新切片的len=cap=512,這也就解釋了為什么資料能讀入b[len(b):cap(b)]
了),觀察下面代碼:
func f2() {
f, _ := os.Open("F:\\hello.txt")
b := make([]byte, 0, 512)
//
c := b[len(b):cap(b)]
fmt.Println(len(c))//512
fmt.Println(cap(c))//512
read, err := f.Read(c)
if err != nil {
return
}
fmt.Println(read)//512
fmt.Println(b)//[]
fmt.Println(b[:cap(b)])//[...] 列印出了資料
fmt.Println(c)//[...]列印出了和上面相同的資料
}
這就奇怪了不是說是參考傳遞嗎,為什么現在c作為引數傳進Read方法后值被改變了,這就需要看切片的內部結構了,切片本身并不承載資料,它只是一個有三個屬性的結構體,傳遞時,就會把這個結構體的三個屬性復制一份進行傳遞,而且復制后頭指標指向相同的地址,另外還有一個重要的概念:對切片的任何操作都會復制一個切片(并不是復制切片資料,二十切片的結構體,他們指向的記憶體區域還是一樣的),也就是復制上面說的三個屬性,讀取切片型別資料的另一個重要屬性就是len,len是多少那就會讀多少資料,雖然由b衍生出的其他結構體他們的頭指標的地址是一樣的,后面的資料也是一樣的,但是如果你的len是0那頭指標后面的資料一個byte也不屬于你,也就讀不出來,你有多少的len那么頭指標后就有多少資料屬于你,
這也就解釋了為什么b始終是空的了,雖然你的頭指標后面有資料被填充了,但是你的len始終是0那么資料都與你無關也是就是空了,c切片的頭指標與b相同但是len和cap不同都是512,所以就能讀取出頭指標后512bytes的資料了,
另外還要討論切片的擴容機制,當切片的len=cap時使用append方法會觸發內置的擴容機制cap會擴大,我就有些疑問為什么是b = append(b, 0)[:len(b)]
,因為使用append函式僅僅是為了觸發擴容,添加進去的0是無意義的,原來len=512現在就變成了513,再往后填充資料就會導致與原資料不一致的問題,因此要把添加的byte去除,
func f3() {
f, _ := os.Open("F:\\hello.txt")
b := make([]byte, 0, 512)
for {
if len(b) == cap(b) {
// Add more capacity (let append pick how much).
//b = append(b, 0)[:len(b)]
b = append(b, 0)
}
n, err := f.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
if err != nil {
if err == io.EOF {
err = nil
}
break
}
}
fmt.Println(string(b))
}
可以看到使用b = append(b, 0)
會導致部分資料失真,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553685.html
標籤:Go
上一篇:驅動開發:內核決議記憶體四級頁表
下一篇:返回列表