自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go 語言切片擴(kuò)容規(guī)則是擴(kuò)容2倍?1.25倍?到底幾倍

開發(fā) 前端
如果當(dāng)前傳入的cap是比原有切片cap的2倍還要大,那么就會按照當(dāng)前傳入的cap來作為新切片的容量,否則去校驗原有切片的容量是否小于 1024。

切片,相信大家用了 Go 語言那么久這這種數(shù)據(jù)類型并不陌生,但是平日里聊到關(guān)于切片是如何擴(kuò)容的,很多人可能會張口就來,切片擴(kuò)容的時候,如果老切片的容量小于 1024 那么就再擴(kuò)容 1倍,也就是新的切片容量是老切片容量的兩倍,同理,如果老切片容量大于 1024,那么就擴(kuò)容1.25 倍

一個人這么說,多個人這么說,你可能就信了????,可是大家都這么認(rèn)為,我們就應(yīng)該盲從嗎?還是要自己去確認(rèn)真實的擴(kuò)容邏輯和實現(xiàn)方式,那就開始吧??

結(jié)論先行,切片對于擴(kuò)容并不一定是 2 倍,1.25倍,這個要看實際情況

本文分別從如下幾點來聊聊切片的擴(kuò)容

  • 擴(kuò)容是針對切片的,數(shù)組無法擴(kuò)容
  • 切片擴(kuò)容到底是擴(kuò)容到原來的幾倍?
  • 我們一般使用切片的時候可以如何避免頻繁的擴(kuò)容?

擴(kuò)容是針對切片的,數(shù)組無法擴(kuò)容

首先需要明確,數(shù)組是不能擴(kuò)容的,數(shù)組定義的時候就已經(jīng)是定長的了,無法擴(kuò)容

切片是可以擴(kuò)容的,我們可以通過 append 追加的方式來向已有的切片尾部進(jìn)行追加,若原有切片已滿,那么就會發(fā)生擴(kuò)容

另外,我們知道數(shù)組是一段連續(xù)的內(nèi)存地址,同一種數(shù)據(jù)類型的數(shù)據(jù)集合,例如這樣

func main() {
   log.SetFlags(log.Lshortfile)
   var demoArray = [5]int{1, 2, 3, 4, 5}
   log.Print("unsafe.sizeof(int) == ",unsafe.Sizeof(demoArray[0]))
   for i, _ := range demoArray {
      log.Printf("&demoAraay[%d] == %p", i, &demoArray[i])
   }
 }

圖片圖片

可以看到在這個案例的環(huán)境中,一個 int 類型的變量占用 8 個字節(jié),自然對于 demoArray 數(shù)組中,地址是連續(xù)的,每一個元素占用的空間也是我們所期望的

那么切片的數(shù)據(jù)地址也是連續(xù)的嗎??

如果有人問這個問題,實際上是想問切片的底層數(shù)組的地址是不是也是連續(xù)的

我們知道,切片 slice 在 Go 中是一個結(jié)構(gòu)體,其中 array 字段是一個指針,指向了一塊連續(xù)的內(nèi)存地址,也就是底層數(shù)組

圖片圖片

type slice struct {
   array unsafe.Pointer
   len   int
   cap   int
}

其中 len 字段記錄了當(dāng)前底層數(shù)組的實際有的元素個數(shù),cap 表示底層數(shù)組的容量,自然也是切片slice 的容量

func main(){
    var demoSli = []int{1,2,3,4,5}
    log.Printf("len == %d,cap == %d",len(demoSli),cap(demoSli))
    for i, _ := range demoSli {
       log.Printf("&demoSli[%d] == %p", i, &demoSli[i])
    }
}

圖片圖片

自然,demoSli 中的元素打印出來,地址也是連續(xù)的,沒有毛病

此處 xdm 模擬的時候,切勿去打印拷貝值的地址,例如下面這種方式是相當(dāng)不明智的

圖片圖片

現(xiàn)在簡單的去給 切片追加一個元素

圖片圖片

可以看到切片的容量變成了原來的兩倍(容量從 5 擴(kuò)容成 10),且切片中底層數(shù)組的元素地址自然也是連續(xù)的,不需要著急下結(jié)論,繼續(xù)往下看,好戲在后頭

切片擴(kuò)容到底是擴(kuò)容到原來的幾倍?

案例1 向一個cap 為 0 的切片中追加 2000 個元素,查看被擴(kuò)容了幾次

圖片圖片

總共是擴(kuò)容了 14 次

可以看到切片容量小于 1024 時,觸發(fā)擴(kuò)容都是擴(kuò)容到原來的 2 倍,但是 大于 1024 之后,有的是 1.25 倍,有的是 1.35 倍,有的大于 1.35 倍,那么這是為什么呢?后面統(tǒng)一看源碼

案例2 再次驗證切片容量小于 1024,觸發(fā)到擴(kuò)容就一定是擴(kuò)容 2 倍嗎

  • 先初始化一個切片,里面有 5 個元素,len 為 5,cap 為 5
  • 再向切片中追加 6 個元素,分別是 6,7,8,9,10,11
  • 最終查看切片的容量是多少
func main(){
    var demoSli = []int{1, 2, 3, 4, 5}
    log.Printf("len == %d,cap == %d", len(demoSli), cap(demoSli))
    for i, _ := range demoSli {
       log.Printf("&demoSli[%d] == %p", i, &demoSli[i])
    }
    
    demoSli = append(demoSli,6,7,8,9,10,11)
    log.Printf("len == %d,cap == %d",len(demoSli),cap(demoSli))
    for i, _ := range demoSli {
       log.Printf("&demoSli[%d] == %p", i, &demoSli[i])
    }
}

通過這一段代碼,我們可以看到,講一個 len 為 5,cap 為 5 的切片,追加數(shù)字 6 的時候,切片應(yīng)該要擴(kuò)容到 10,然后追加到數(shù)字 11 的時候,切片應(yīng)該擴(kuò)容到 20,可實際真的是這樣嗎?

圖片圖片

xdm 可以將上述 demo 貼到自己環(huán)境試試,得到的結(jié)果仍然會是切片的容量 cap 最終是 12,并不是 20

那么這一切都是為什么呢?我們來查看源碼一探究竟

源碼賞析

查看公共庫中 runtime/slice.go 的 growslice 函數(shù)就可以解開我們的疑惑

圖片圖片

可以看出在我們使用 append 對切片追加元素的時候,實際上會調(diào)用到 growslice 函數(shù), growslice 中的核心邏輯我們就可以理解為計算基本的 newcap 和進(jìn)行字節(jié)對齊

  1. 進(jìn)行基本的新切片容量計算
// 省略部分
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
   newcap = cap
} else {
   if old.cap < 1024 {
      newcap = doublecap
   } else {
      // Check 0 < newcap to detect overflow
      // and prevent an infinite loop.
      for 0 < newcap && newcap < cap {
         newcap += newcap / 4
      }
      // Set newcap to the requested cap when
      // the newcap calculation overflowed.
      if newcap <= 0 {
         newcap = cap
      }
   }
}
// 省略部分

此處邏輯可以知道

  • 如果當(dāng)前傳入的 cap 是比原有切片 cap 的 2 倍還要大,那么就會按照當(dāng)前傳入的 cap 來作為新切片的容量
  • 否則去校驗原有切片的容量是否小于 1024

若小于 1024 ,則按照原有的切片容量的 2 倍進(jìn)行擴(kuò)容

若大于等于 1024 ,那么就按照原有切片的 1.25 倍繼續(xù)擴(kuò)容

然后是否看到這里就就結(jié)束了呢?就下定論來呢?并不,我們切莫斷章取義,需要看全整個流程

  1. 進(jìn)行基本的字節(jié)對齊

growslice 函數(shù) 計算出基本的 newcap 之后,還需要按照類型進(jìn)行基本的字節(jié)對齊,此處字節(jié)對齊之后主要是 roundupsize 的函數(shù)實現(xiàn),順便將其涉及到的常量放到一起給大家展示一波

const (
   _MaxSmallSize = 32768
   smallSizeDiv = 8
   smallSizeMax = 1024
   largeSizeDiv = 128
   _NumSizeClasses = 68
   _PageShift = 13
)
func roundupsize(size uintptr) uintptr {
   if size < _MaxSmallSize {
      if size <= smallSizeMax-8 {
         return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])
      } else {
         return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]])
      }
   }
   if size+ _PageSize < size {
      return size
   }
   return alignUp(size, _PageSize)
}

func divRoundUp(n, a uintptr) uintptr {
   // a is generally a power of two. This will get inlined and
   // the compiler will optimize the division.
   return (n + a - 1) / a
}
var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, ...}

光看這個函數(shù),沒啥感覺,函數(shù)邏輯比較簡單,就是基本的計算和索引,那么我們講上述的案例2帶入,來計算一下

圖片圖片

此處很明確,當(dāng)前舊的切片的 cap 為 5

也就是 growslice 函數(shù) 中 old.cap 為 5,傳入的 cap 為 11,因此 cap > 2*old.cap

因此 newcap 此處等于 11

圖片圖片

開始計算字節(jié)對齊之后的結(jié)果

  • roundupsize(uintptr(newcap) * sys.PtrSize) ,其中 newcap = 11,sys.PtrSize = 8,則 roundupsize 參數(shù)傳入 88 ,此環(huán)境指針占用 8 字節(jié)
  • 按照如下邏輯進(jìn)行計算

divRoundUp(88, 8) = 11

size_to_class8[11] = 8

class_to_size[8] = 96

此處環(huán)境我們的 int 類型是占用 8 個字節(jié),因此最終的 newcap = 96/8 = 12

圖片圖片

經(jīng)過上述源碼的處理,最終我們就可以正常的得到最終切片容量被擴(kuò)容到 12 ,xdm 可以去看實際的源碼

小結(jié)

使用 append 進(jìn)行切片擴(kuò)容的時候,先會按照基本的邏輯來計算 newcap 的大小

  • 如果當(dāng)前傳入的cap是比原有切片cap的2倍還要大,那么就會按照當(dāng)前傳入的cap來作為新切片的容量,否則去校驗原有切片的容量是否小于 1024
  • 若小于1024,則按照原有的切片容量的2倍進(jìn)行擴(kuò)容
  • 若大于等于 1024,那么就按照原有切片的 1.25 倍繼續(xù)擴(kuò)容最終再進(jìn)行字節(jié)對齊

那么實際上,最終的切片容量一般是會等于或者大于原有的 2倍 或者是 1.25 倍的

我們一般使用切片的時候可以如何避免頻繁的擴(kuò)容?

一般在使用切片的時候,盡量避免頻繁的去擴(kuò)容,我們可以對已知數(shù)據(jù)量的數(shù)據(jù),進(jìn)行一次性去分配切片的容量

例如,數(shù)據(jù)量有 1000 個,那么我們就可以使用 make 的方式來進(jìn)行初始化

sli := make([]int, 0, 1000)

本次就是這樣,如果對源碼還挺感興趣的話,xdm 可以去實際查看一下源碼哦,希望對你有幫助

責(zé)任編輯:武曉燕 來源: 阿兵云原生
相關(guān)推薦

2023-04-03 08:02:16

切片擴(kuò)容GO

2017-05-27 09:35:56

康復(fù)機(jī)器人兒童福利院

2021-03-04 09:25:08

Go語言惡意軟件黑客

2018-03-28 14:10:10

GoPython代碼

2021-03-29 15:59:52

區(qū)塊鏈比特幣擴(kuò)容

2020-10-23 06:56:00

C語言動態(tài)字符串

2021-11-19 11:36:42

語言string字符串

2023-03-13 13:36:00

Go擴(kuò)容切片

2025-04-14 10:10:00

磁盤Linux磁盤擴(kuò)容

2010-09-01 14:00:01

DB2表空間

2022-04-06 08:19:13

Go語言切片

2023-03-29 08:03:53

2013-07-10 17:27:07

OTN技術(shù)華為OTN華為

2022-10-27 08:31:31

架構(gòu)

2021-07-13 06:44:04

Go語言數(shù)組

2018-08-20 08:15:50

編程語言Go語言切片

2021-06-07 11:40:26

Python命令代碼

2022-01-21 14:26:05

區(qū)塊鏈鏈上鏈下

2010-11-03 11:30:56

DB2表空間擴(kuò)容

2018-01-14 23:07:36

戴爾
點贊
收藏

51CTO技術(shù)棧公眾號