本文目錄一覽:
- 1、Golang|切片原理
- 2、golang-101-hacks(12)——切片作為函數參數傳遞
- 3、golang函數返回slice和返回 slice的指針有什麼區別
- 4、Golang 中數組(Array)和切片(Slice)的區別
- 5、GoLang中的切片擴容機制
Golang|切片原理
在Golang語言開發過程中,我們經常會用到數組和切片數據結構,數組是固定長度的,而切片是可以擴張的數組,那麼切片底層到底有什麼不同?接下來我們來詳細分析一下內部實現。
首先我們來看一下數據結構
這裡的array其實是指向切片管理的內存塊首地址,而len就是切片的實際使用大小,cap就是切片的容量。
我們可以通過下面的代碼輸出slice:
這麼分析下來,我們可以了解如下內容:
使用一個切片通常有兩種方法:
另一種是slice = make([]int, len, cap)這種方法,稱為分配內存。
創建一個slice,實質上是在分配內存。
這裡跟一下細節,math.MulUintptr是基於底層的指針計算乘法的,這樣計算不會導致超出int大小,這個方法在後面會經常用到。
同樣,對於int64的長度,也有對應的方法
而實際分配內存的操作調用mallocgc這個分配內存的函數,這個函數以後再分析。
我們了解切片和數組最大的不同就是切片能夠自動擴容,接下來看看切片是如何擴容的
這裡可以看到,growslice是返回了一個新的slice,也就是說如果發生了擴容,會發生拷貝。
所以我們在使用過程中,如果預先知道容量,可以預先分配好容量再使用,能提高運行效率。
copy這個函數在內部實現為slicecopy
還有關於字元串的拷貝
這裡顯示了可以把string拷貝成[]byte,不能把[]byte拷貝成string。
1、切片的數據結構是 array內存地址,len長度,cap容量
2、make的時候需要注意 容量 * 長度 分配的內存大小要小於264,並且要小於可分配的內存量,同時長度不能大於容量。
3、內存增長的過程:
4、當發生內存擴容時,會發生拷貝數據的現象,影響程序運行的效率,如果可以,要先分配好指定的容量
5、關於拷貝,可以把string拷貝成[]byte,不能把[]byte拷貝成string。
golang-101-hacks(12)——切片作為函數參數傳遞
註:本文是對 golang-101-hacks 中文翻譯。
在Go語言中,函數參數是值傳遞。使用slice作為函數參數時,函數獲取到的是slice的副本:一個指針,指向底層數組的起始地址,同時帶有slice的長度和容量。既然各位熟知數據存儲的內存的地址,現在可以對切片數據進行修改。讓我們看看下面的例子:
In Go, the function parameters are passed by value. With respect to use slice as a function argument, that means the function will get the copies of the slice: a pointer which points to the starting address of the underlying array, accompanied by the length and capacity of the slice. Oh boy! Since you know the address of the memory which is used to store the data, you can tweak the slice now. Let’s see the following example:
運行結果如下
由此可見,執行modifyValue函數,切片s的元素髮生了變化。儘管modifyValue函數只是操作slice的副本,但是任然改變了切片的數據元素,看另一個例子:
You can see, after running modifyValue function, the content of slice s is changed. Although the modifyValue function just gets a copy of the memory address of slice’s underlying array, it is enough!
See another example:
The result is like this:
而這一次,addValue函數並沒有修改main函數中的切片s的元素。這是因為它只是操作切片s的副本,而不是切片s本身。所以如果真的想讓函數改變切片的內容,可以傳遞切片的地址:
This time, the addValue function doesn’t take effect on the s slice in main function. That’s because it just manipulate the copy of the s, not the “real” s.
So if you really want the function to change the content of a slice, you can pass the address of the slice:
運行結果如下
golang函數返回slice和返回 slice的指針有什麼區別
按照你的定義,slice是切片,而p是指針。切片是一個結構體頭部+數組區域,其頭部結構定義如下:
struct Slice
{ // must not move anything
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; // allocated number of elements
};
因此,slice的返回其實是頭部值返回,函數內外的地址是不同的,這也導致主程序中,ss與pp不同。因為ss是新分配的,pp則是與子程序testInterface中的slice相同。簡單修改你的代碼,通過輸出對比,會非常清晰:
package main
import (
“fmt”
)
func testInterface() (slice interface{}, p interface{}) {
slice = make([]int, 10)
p = slice
fmt.Println(“debug:testInterface”)
fmt.Println(slice)//兩個地址應該相同
fmt.Println(p) //兩個地址應該相同
return slice, p
}
func main() {
fmt.Println(“debug:main”)
ss, pp := testInterface()
fmt.Println(ss)
fmt.Println(pp) //應該與子程序的輸出一致
}
另外,第一個問題就不用多解釋,依然是值和指針不同了。
Golang 中數組(Array)和切片(Slice)的區別
Go 中數組的長度是不可改變的,而 Slice 解決的就是對不定長數組的需求。他們的區別主要有兩點。
數組:
切片:
注意 1
雖然數組在初始化時也可以不指定長度,但 Go 語言會根據數組中元素個數自動設置數組長度,並且不可改變。切片通過 append 方法增加元素:
如果將 append 用在數組上,你將會收到報錯:first argument to append must be slice。
注意 2
切片不只有長度(len)的概念,同時還有容量(cap)的概念。因此切片其實還有一個指定長度和容量的初始化方式:
這就初始化了一個長度為3,容量為5的切片。
此外,切片還可以從一個數組中初始化(可應用於如何將數組轉換成切片):
上述例子通過數組 a 初始化了一個切片 s。
當切片和數組作為參數在函數(func)中傳遞時,數組傳遞的是值,而切片傳遞的是指針。因此當傳入的切片在函數中被改變時,函數外的切片也會同時改變。相同的情況,函數外的數組則不會發生任何變化。
GoLang中的切片擴容機制
[5]int 是數組,而 []int 是切片。二者看起來相似,實則是根本上不同的數據結構。
切片的數據結構中,包含一個指向數組的指針 array ,當前長度 len ,以及最大容量 cap 。在使用 make([]int, len) 創建切片時,實際上還有第三個可選參數 cap ,也即 make([]int, len, cap) 。在不聲明 cap 的情況下,默認 cap=len 。當切片長度沒有超過容量時,對切片新增數據,不會改變 array 指針的值。
當對切片進行 append 操作,導致長度超出容量時,就會創建新的數組,這會導致和原有切片的分離。在下例中
由於 a 的長度超出了容量,所以切片 a 指向了一個增長後的新數組,而 b 仍然指向原來的老數組。所以之後對 a 進行的操作,對 b 不會產生影響。
試比較
本例中, a 的容量為6,因此在 append 後並未超出容量,所以 array 指針沒有改變。因此,對 a 進行的操作,對 b 同樣產生了影響。
下面看看用 a := []int{} 這種方式來創建切片會是什麼情況。
可以看到,空切片的容量為0,但後面向切片中添加元素時,並不是每次切片的容量都發生了變化。這是因為,如果增大容量,也即需要創建新數組,這時還需要將原數組中的所有元素複製到新數組中,開銷很大,所以GoLang設計了一套擴容機制,以減少需要創建新數組的次數。但這導致無法很直接地判斷 append 時是否創建了新數組。
如果一次添加多個元素,容量又會怎樣變化呢?試比較下面兩個例子:
那麼,是不是說,當向一個空切片中插入 2n-1 個元素時,容量就會被設置為 2n 呢?我們來試試其他的數據類型。
可以看到,根據切片對應數據類型的不同,容量增長的方式也有很大的區別。相關的源碼包括: src/runtime/msize.go , src/runtime/mksizeclasses.go 等。
我們再看看切片初始非空的情形。
可以看到,與剛剛向空切片添加5個int的情況一致,向有3個int的切片中添加2個int,容量增長為6。
需要注意的是, append 對切片擴容時,如果容量超過了一定範圍,處理策略又會有所不同。可以看看下面這個例子。
具體為什麼會是這樣的變化過程,還需要從 源碼 中尋找答案。下面是 src/runtime/slice.go 中的 growslice 函數中的核心部分。
GoLang中的切片擴容機制,與切片的數據類型、原本切片的容量、所需要的容量都有關係,比較複雜。對於常見數據類型,在元素數量較少時,大致可以認為擴容是按照翻倍進行的。但具體情況需要具體分析。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/304683.html