今年3月初,騰訊發布了《騰訊研發大數據報告》,筆者發現GO語言的使用在鵝廠已經上升到了TOP5的位置了。
我們知道騰訊尤其是Docker容器化這一塊,是走在各大廠的前列的,尤其是他們的基于GO語言開發的DEVOPS藍鯨平臺,水平相當高。
經筆者實地上手體驗,GO語言在并發等方面還是相當優秀的,下面筆者就匯報一下最新的成果。
一、GO語言的切片簡介
切片(slice)是對數組的一個連續片段的引用,所以切片是一個引用類型同,與Python 中的 list 類型比較類似,這個片段可以是整個數組,也可以是由起始和終止索引標識的一些項的子集。Go語言中切片的內部結構包含地址、大小(len)和容量(cap)與數組相比切片最大的特點就是其容量是可變的。
二、GO語言的代碼解讀
1. append函數添加元素
Go語言的內建函數 append() 可以為切片動態添加元素,不過需要注意的是,由于切片本身是變長的,因此在使用 append() 函數為切片動態添加元素時,切片就會自動進行“擴容”,同時新切片的長度也會增加,但是有一點需要注意,append返回的是一個新的切片對象,而不是對原切片進行操作。在下面的代碼中我們先定義了一個切片a,并不斷通過append方式為其增加元素,并觀察切片a的長度及容量變化。
package main import ( "fmt" ) func main() { var a []int //定義一個切片 fmt.Printf("len: %d cap: %d pointer: %p\n", len(a), cap(a), a)//此時切片長度和容量都是0,運行結果為len: 0 cap: 0 pointer: 0x0 a = append(a, 1) // 追加1個元素 fmt.Printf("len: %d cap: %d pointer: %p\n", len(a), cap(a), a)//注意此時a的地址已經發生變化為新的切片了,新切片長度和容量都為1運行結果為:len: 1 cap: 1 pointer: 0xc000072098 a = append(a, 2, 3, 4) // 追加多個元素 fmt.Printf("len: %d cap: %d pointer: %p\n", len(a), cap(a), a)//注意此時a的地址再次發生變化實際上又生成為新的切片了,新切片長度和容量都為4運行結果為:len: 4 cap: 4 pointer: 0xc000070160 a = append(a, 5) // 再追加一個元素 fmt.Printf("len: %d cap: %d pointer: %p\n", len(a), cap(a), a)//注意切片擴容策略是倍增方式容量由4變成8,而長度是5運行結果為:len: 4 cap: 4 pointer: 0xc000070160 }
可以觀察到切片在擴容時,其容量(cap)的速度規律是以2 倍數進行的。
2.在切片中元素的刪除
刪除切片中開頭的N個元素
使用x = x[N:] 的方式來在切片中刪除由第i個元素開始的N個元素
具體代碼如下:
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //使用原始定義法來聲明并初始化一個切片
fmt.Println(a) //運行結果為[1 2 3 4 5 6 7 8 9 10]
a = a[1:] // 刪除第1個元素
fmt.Println(a) //刪頭第1個元素后,運行結果為[2 3 4 5 6 7 8 9 10]
a = a[2:] // 刪除前2個元素
fmt.Println(a) //刪頭前2個元素后,運行結果為[4 5 6 7 8 9 10]
}
3、深入理解GO語言中的切片
有關切片的代碼位置在GOPATH\src\runtime\slice.go,其中對于幾個重點函數解讀如下:
1.slice 結構定義
首先slice是這樣一個結構體,他有一個存放數據的數組,和一個長度len與容量cap構成
type slice struct {
array unsafe.Pointer
len int
cap int
}
2.創建切片的makeslice函數
而創建切片的函數makeslice如下,可以看到函數會對于內存進行預分配,如果成功再正式分配內存,他建切片的makeslice函數源碼及注釋如下:
func makeslice(et *_type, len, cap int) slice {
mem, overflow := math.MulUintptr(et.size, uintptr(cap))//此函數計算et.size也就是每個元素所占空間的大小,并與容量cap相乘,其中mem既為所需要最大內存,overflow代表是否會造成溢出
if overflow || mem > maxAlloc || len < 0 || len > cap {//判斷是否有溢出,長度為負數或者長度比容量大的情況,如存在 直接panic
// NOTE: Produce a 'len out of range' error instead of a
// 'cap out of range' error when someone does make([]T, bignumber).
// 'cap out of range' is true too, but since the cap is only being
// supplied implicitly, saying len is clearer.
// See golang.org/issue/4085.
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
return mallocgc(mem, et, true)// 如果錯誤檢查成功,則分配內存,注意slice對象會被GC所自動清除。
}
3.擴容函數growslice
通過閱讀growslice的源碼可以看在這個函數當中,擴容的規則是在長度小于1024時按照一直采用的是翻倍的方式進行擴容,在大于1024后,每次擴容至原容量的1.25倍,新容量計算完成后對于內存進行預分配,這點也makeslice的想法一致,接下再將老slice中的數據通過memmove(p, old.array, lenmem)的方式拷貝至新的slice。growlice函數源碼及注釋如下:
func growslice(et *_type, old slice, cap int) slice {
// 單純地擴容,不寫數據
if et.size == 0 {
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
// append should not create a slice with nil pointer but non-zero len.
// We assume that append doesn't need to preserve old.array in this case.
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
// 擴容規則 1.新的容量大于舊的2倍,直接擴容至新的容量
// 2.新的容量不大于舊的2倍,當舊的長度小于1024時,擴容至舊的2倍,否則擴容至舊的1.25倍
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for newcap < cap {
newcap += newcap / 4
}
}
}
// 跟據切片類型和容量計算要分配內存的大小
var overflow bool
var lenmem, newlenmem, capmem uintptr
switch {
case et.size == 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
overflow = uintptr(newcap) > maxAlloc
newcap = int(capmem)
case et.size == sys.PtrSize:
lenmem = uintptr(old.len) * sys.PtrSize
newlenmem = uintptr(cap) * sys.PtrSize
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
newcap = int(capmem / sys.PtrSize)
case isPowerOfTwo(et.size):
var shift uintptr
if sys.PtrSize == 8 {
// Mask shift for better code generation.
shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
} else {
shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
}
lenmem = uintptr(old.len) << shift
newlenmem = uintptr(cap) << shift
capmem = roundupsize(uintptr(newcap) << shift)
overflow = uintptr(newcap) > (maxAlloc >> shift)
newcap = int(capmem >> shift)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
capmem = roundupsize(capmem)
newcap = int(capmem / et.size)
}
// 異常情況,舊的容量比新的容量還大或者新的容量超過限制了
if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
panic(errorString("growslice: cap out of range"))
}
var p unsafe.Pointer
if et.kind&kindNoPointers != 0 {
// 為新的切片開辟容量為capmem的地址空間
p = mallocgc(capmem, nil, false)
// 將舊切片的數據搬到新切片開辟的地址中
memmove(p, old.array, lenmem)
// The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
// Only clear the part that will not be overwritten.
// 清理下新切片中剩余地址,不能存放堆棧指針
// memclrNoHeapPointers clears n bytes starting at ptr.
//
// Usually you should use typedmemclr. memclrNoHeapPointers should be
// used only when the caller knows that *ptr contains no heap pointers
// because either:
//
// 1. *ptr is initialized memory and its type is pointer-free.
//
// 2. *ptr is uninitialized memory (e.g., memory that's being reused
// for a new allocation) and hence contains only "junk".
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
p = mallocgc(capmem, et, true)
if !writeBarrier.enabled {
memmove(p, old.array, lenmem)
} else {
for i := uintptr(0); i < lenmem; i += et.size {
typedmemmove(et, add(p, i), add(old.array, i))
}
}
}
return slice{p, old.len, newcap}
}
三、GO語言切片的相關結論
所以通過閱讀以上源代碼我們也可以知道,有以下兩點結論:
- append方式為數據增加元素時,如果觸發切片進行擴容,則肯定是新生成了一個切片對象,并且涉及內存操作,因此append操作一定要小心。
- 建議盡量通過make函數來聲明一個切片,并在初始時盡量設定好一個合理的容量值,避免切片頻繁擴容帶來不必要的開銷。
掃碼二維碼 獲取免費視頻學習資料
- 本文固定鏈接: http://www.wangchenghua.com/post/7323/
- 轉載請注明:轉載必須在正文中標注并保留原文鏈接
- 掃碼: 掃上方二維碼獲取免費視頻資料