日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

詳解Go語言中切片的長度與容量的區別_Golang

作者:TimLiu ? 更新時間: 2022-12-05 編程語言

切片的聲明

切片可以看成是數組的引用(實際上切片的底層數據結構確實是數組)。在?Go?中,每個數組的大小是固定的,不能隨意改變大小,切片可以為數組提供動態增長和縮小的需求,但其本身并不存儲任何數據。

// 數組的聲明
var a [5]int //只指定長度,元素初始化為默認值0
var a [5]int{1,2,3,4,5}

// 切片的聲明
// 方法1:直接初始化
var s []int //聲明一個長度和容量為 0 的 nil 切片
var s []int{1,2,3,4,5} // 同時創建一個長度為5的數組
// 方法2:用make()函數來創建切片
var s = make([]int, 0, 5)

// 切分數組:var 變量名 []變量類型 = arr[low, high],low和high為數組的索引。
// 記住規則為:左閉右開
var arr = [5]int{1,2,3,4,5}
var slice []int = arr[1:4] // [2,3,4]

切片的長度和容量

切片的長度是它所包含的元素個數。切片的容量是從它的第一個元素到其底層數組元素末尾的個數。切片?s?的長度和容量可通過表達式?len(s)?和?cap(s)?來獲取。

s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(s, len(s), cap(s))
// output: [0 1 2 3 4 5 6 7 8 9] 10 10

s1 := s[0:5]
fmt.Println(s1, len(s1), cap(s1))
// output: [0 1 2 3 4] 5 10

s2 := s[5:]
fmt.Println(s2, len(s2), cap(s2))
// output: [5 6 7 8 9] 5 5

切片追加元素后長度和容量的變化

append 函數

Go?提供了內建的?append?函數,為切片追加新的元素。

func append(s []T, vs ...T) []T

append?的返回值是一個包含原切片所有元素加上新添加元素的切片。

s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(s, len(s), cap(s))
sResult := append(s, 11)
fmt.Println(sResult, len(sResult), cap(sResult))
// output: 
// [0 1 2 3 4 5 6 7 8 9] 10 10
// [0 1 2 3 4 5 6 7 8 9 11] 11 20

這個時候,我們就可以發現,當我們?append?元素進入切片時,原切片的長度以及容量都發生了變化,但是它們的變化為什么會這樣呢?

下面我們一起看看源碼是怎么實現的。

切片的源代碼學習

Go?中切片的數據結構可以在源碼下的?src/runtime/slice.go?中查看。以下源代碼基于?go1.16.7?版本。

切片的結構體

切片作為數組的引用,有三個屬性字段:指向數組的指針、長度和容量。

type slice struct {
  // 指向底層數組的指針
	array unsafe.Pointer
  // slice 當前元素個數,即 len() 時返回的數
	len   int
  // slice 的容量,即 cap() 時返回的數
	cap   int
}

切片的擴容

slice?通過調用?append?函數來針對slice進行尾部追加元素,如果此時?slice?的?cap?值小于當前?len?加上?append?中傳入值的數量,就會調用?runtime.growslice?函數,進行擴容。

我們這里只放出基本的擴容規則的代碼解析,如果對內存對齊、數據拷貝等感興趣,可自行查看對應的源碼。

基本擴容規則

func growslice(et *_type, old slice, cap int) slice {
    newcap := old.cap
    doublecap := newcap + newcap
    // 如果新容量大于舊容量的兩倍,則直接按照新容量大小申請
    if cap > doublecap {
			newcap = cap
    } else {
        // 如果原有長度小于1024,則新容量是舊容量的2倍
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // 按照原有容量的 1/4 增加,直到滿足新容量的需要
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
}

從源碼來看,實際上可以整理出幾個規則:

當原切片長度小于 1024 時,新的切片長度直接加上?append?元素的個數,容量則會直接?*2

當原切片長度大于等于 1024 時,新的切片長度直接加上?append?元素的個數,容量則會增加?1/4

總結

切片是一個結構體,保存著切片的容量,長度以及指向數組的指針(數組的地址)。

從源碼來看,當一個切片進行擴容時,會進行 growslice,這是一個花銷較大的操作,在日常開發中,如果能明確知道切片的長度或者容量時,我們需要在初始化的時候聲明,避免切片頻繁擴容而帶來的花銷。

原文鏈接:https://mp.weixin.qq.com/s/G3pozktEi3hCxzp9rfVD0g

欄目分類
最近更新