网站首页 > 知识剖析 正文
Slice 使?和扩容
Slice 是 Go 语?中的?个重要数据类型,它是对数组的?个抽象,提供了更加强?的功能。Slice 本身并不是?个数组,它指向底层数组。Slice 的功能很强?
go 版本:go1.21.2 darwin/amd64
Slice 的底层数组
Slice 是对数组的?个抽象,它指向底层数组。Slice 的底层数组是?个连续的内存空间,Slice 的元素类型都是相同的。Slice 的底层数组的地址是 Slice 的第?个元素的地址。
Slice 的结构
Slice 源码如下:
/src/runtime/slice.go
type slice struct { array unsafe.Pointer len int
cap int
}
Slice 有三个字段,分别是 array、len 和 cap。array 是?个指向底层数组的指针,len 表示 Slice 的?度,cap 表示 Slice 的容量。
Slice 的定义
Slice 的定义和数组类似,只是不需要?度。Slice 的定义格式如下:
var myslice []int
使?make 函数创建?个 Slice,格式如下:
myslice := make([]int, 10)
[]int 表示 Slice 的类型,10 表示 Slice 的?度,cap(myslice) 表示 Slice 的容量。
Slice 的扩容
Slice 的扩容是指 Slice 的?度超过了 Slice 的容量。当 Slice 的?度超过了 Slice 的容量时,Go 语?会?动扩容
Slice 的容量。
使?append追加元素到 Slice 时
myslice = append(myslice, 1)
扩容的源码如下:
func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice { oldLen := newLen - num
if raceenabled {
callerpc := getcallerpc()
racereadrangepc(oldPtr, uintptr(oldLen*int(et.Size_)), callerpc, abi.FuncPCABIInternal(growslice))
}
if msanenabled {
msanread(oldPtr, uintptr(oldLen*int(et.Size_)))
}
if asanenabled {
asanread(oldPtr, uintptr(oldLen*int(et.Size_)))
}
if newLen < 0 {
panic(errorString("growslice: len out of range"))
}
if et.Size_ == 0 {
// append should not create a slice with nil pointer but non-zero len.
// We assume that append doesn't need to preserve oldPtr in this case. return slice{unsafe.Pointer(&zerobase), newLen, newLen}
}
newcap := oldCap
doublecap := newcap + newcap if newLen > doublecap {
newcap = newLen
} else {
const threshold = 256 if oldCap < threshold {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop. for 0 < newcap && newcap < newLen {
// Transition from growing 2x for small slices
// to growing 1.25x for large slices. This formula
// gives a smooth-ish transition between the two. newcap += (newcap + 3*threshold) / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed. if newcap <= 0 {
newcap = newLen
}
}
}
var overflow bool
var lenmem, newlenmem, capmem uintptr
// Specialize for common values of et.Size.
// For 1 we don't need any division/multiplication.
// For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
// For powers of 2, use a variable shift. switch {
case et.Size_ == 1:
lenmem = uintptr(oldLen) newlenmem = uintptr(newLen)
capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem)
case et.Size_ == goarch.PtrSize:
lenmem = uintptr(oldLen) * goarch.PtrSize newlenmem = uintptr(newLen) * goarch.PtrSize
capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize)
case isPowerOfTwo(et.Size_): var shift uintptr
if goarch.PtrSize == 8 {
// Mask shift for better code generation.
shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63
} else {
shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31
}
lenmem = uintptr(oldLen) << shift newlenmem = uintptr(newLen) << shift
capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift)
capmem = uintptr(newcap) << shift default:
lenmem = uintptr(oldLen) * et.Size_ newlenmem = uintptr(newLen) * et.Size_
capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap)) capmem = roundupsize(capmem)
newcap = int(capmem / et.Size_) capmem = uintptr(newcap) * et.Size_
}
// The check of overflow in addition to capmem > maxAlloc is needed
// to prevent an overflow which can be used to trigger a segfault
// on 32bit architectures with this example program:
//
// type T [1<<27 + 1]int64
//
// var d T
// var s []T
//
// func main() {
// s = append(s, d, d, d, d)
// print(len(s), "\n")
// }
if overflow || capmem > maxAlloc { panic(errorString("growslice: len out of range"))
}
var p unsafe.Pointer if et.PtrBytes == 0 {
p = mallocgc(capmem, nil, false)
// The append() that calls growslice is going to overwrite from oldLen to newLen.
// Only clear the part that will not be overwritten.
// The reflect_growslice() that calls growslice will manually clear
// the region not cleared here. 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 lenmem > 0 && writeBarrier.enabled {
// Only shade the pointers in oldPtr since we know the destination slice p
// only contains nil pointers because it has been cleared during alloc. bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-et.Size_+et.PtrBytes)
}
}
memmove(p, oldPtr, lenmem)
return slice{p, newLen, newcap}
}
growslice 函数的参数 oldPtr 是 Slice 的底层数组指针,newLen 是 Slice 的新?度,oldCap 是 Slice 的旧容量,
num 是 Slice 的增??度,et 是 Slice 的元素类型。
扩容的逻辑如下:
- 如果 newLen ?于 0,抛出异常。代码如下:
if newLen < 0 {
panic(errorString("growslice: len out of range"))
}
- 如果 Slice 的元素类型的??为 0,返回?个新的 Slice。代码如下:
if et.Size_ == 0 {
return slice{unsafe.Pointer(&zerobase), newLen, newLen}
}
- 计算新的容量 newcap。代码如下:
newcap := oldCap
doublecap := newcap + newcap if newLen > doublecap {
newcap = newLen
} else {
const threshold = 256 if oldCap < threshold {
newcap = doublecap
} else {
for 0 < newcap && newcap < newLen { newcap += (newcap + 3*threshold) / 4
}
if newcap <= 0 { newcap = newLen
}
}
}
如果 Slice 的容量?于 1024,则新的 Slice 的容量是原来的 2 倍。如果 Slice 的容量?于等于 1024,则新的 Slice
的容量是原来的 1.25 倍,直到新的 Slice 的容量?于等于 newLen。
- 计算新的 Slice 的内存??。代码如下:
var overflow bool
var lenmem, newlenmem, capmem uintptr switch {
case et.Size_ == 1:
lenmem = uintptr(oldLen) newlenmem = uintptr(newLen)
capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem)
case et.Size_ == goarch.PtrSize:
lenmem = uintptr(oldLen) * goarch.PtrSize newlenmem = uintptr(newLen) * goarch.PtrSize
capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize)
case isPowerOfTwo(et.Size_): var shift uintptr
if goarch.PtrSize == 8 {
shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63
} else {
shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31
}
lenmem = uintptr(oldLen) << shift newlenmem = uintptr(newLen) << shift
capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift)
capmem = uintptr(newcap) << shift default:
lenmem = uintptr(oldLen) * et.Size_ newlenmem = uintptr(newLen) * et.Size_
capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap)) capmem = roundupsize(capmem)
newcap = int(capmem / et.Size_) capmem = uintptr(newcap) * et.Size_
}
- 如果 newcap ?于 maxAlloc,抛出异常。代码如下:
if overflow || capmem > maxAlloc { panic(errorString("growslice: len out of range"))
}
- 分配新的内存。代码如下:
var p unsafe.Pointer if et.PtrBytes == 0 {
p = mallocgc(capmem, nil, false) memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
p = mallocgc(capmem, et, true)
if lenmem > 0 && writeBarrier.enabled { bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-
et.Size_+et.PtrBytes)
}
}
memmove(p, oldPtr, lenmem)
- 返回新的 Slice。# Slice 使?和扩容
Slice 是 Go 语?中的?个重要数据类型,它是对数组的?个抽象,提供了更加强?的功能。Slice 本身并不是?个数组,它指向底层数组。Slice 的功能很强?
go 版本:go1.21.2 darwin/amd64
Slice 的底层数组
Slice 是对数组的?个抽象,它指向底层数组。Slice 的底层数组是?个连续的内存空间,Slice 的元素类型都是相同的。Slice 的底层数组的地址是 Slice 的第?个元素的地址。
Slice 的结构
Slice 源码如下:
/src/runtime/slice.go
type slice struct { array unsafe.Pointer len int
cap int
}
Slice 有三个字段,分别是 array、len 和 cap。array 是?个指向底层数组的指针,len 表示 Slice 的?度,cap 表示 Slice 的容量。
Slice 的定义
Slice 的定义和数组类似,只是不需要?度。Slice 的定义格式如下:
var myslice []int
使?make 函数创建?个 Slice,格式如下:
myslice := make([]int, 10)
[]int 表示 Slice 的类型,10 表示 Slice 的?度,cap(myslice) 表示 Slice 的容量。
Slice 的扩容
Slice 的扩容是指 Slice 的?度超过了 Slice 的容量。当 Slice 的?度超过了 Slice 的容量时,Go 语?会?动扩容
Slice 的容量。
使?append追加元素到 Slice 时
myslice = append(myslice, 1)
扩容的源码如下:
func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice { oldLen := newLen - num
if raceenabled {
callerpc := getcallerpc()
racereadrangepc(oldPtr, uintptr(oldLen*int(et.Size_)), callerpc, abi.FuncPCABIInternal(growslice))
}
if msanenabled {
msanread(oldPtr, uintptr(oldLen*int(et.Size_)))
}
if asanenabled {
asanread(oldPtr, uintptr(oldLen*int(et.Size_)))
}
if newLen < 0 {
panic(errorString("growslice: len out of range"))
}
if et.Size_ == 0 {
// append should not create a slice with nil pointer but non-zero len.
// We assume that append doesn't need to preserve oldPtr in this case. return slice{unsafe.Pointer(&zerobase), newLen, newLen}
}
newcap := oldCap
doublecap := newcap + newcap if newLen > doublecap {
newcap = newLen
} else {
const threshold = 256 if oldCap < threshold {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop. for 0 < newcap && newcap < newLen {
// Transition from growing 2x for small slices
// to growing 1.25x for large slices. This formula
// gives a smooth-ish transition between the two. newcap += (newcap + 3*threshold) / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed. if newcap <= 0 {
newcap = newLen
}
}
}
var overflow bool
var lenmem, newlenmem, capmem uintptr
// Specialize for common values of et.Size.
// For 1 we don't need any division/multiplication.
// For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
// For powers of 2, use a variable shift. switch {
case et.Size_ == 1:
lenmem = uintptr(oldLen) newlenmem = uintptr(newLen)
capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem)
case et.Size_ == goarch.PtrSize:
lenmem = uintptr(oldLen) * goarch.PtrSize newlenmem = uintptr(newLen) * goarch.PtrSize
capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize)
case isPowerOfTwo(et.Size_): var shift uintptr
if goarch.PtrSize == 8 {
// Mask shift for better code generation.
shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63
} else {
shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31
}
lenmem = uintptr(oldLen) << shift newlenmem = uintptr(newLen) << shift
capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift)
capmem = uintptr(newcap) << shift default:
lenmem = uintptr(oldLen) * et.Size_ newlenmem = uintptr(newLen) * et.Size_
capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap)) capmem = roundupsize(capmem)
newcap = int(capmem / et.Size_) capmem = uintptr(newcap) * et.Size_
}
// The check of overflow in addition to capmem > maxAlloc is needed
// to prevent an overflow which can be used to trigger a segfault
// on 32bit architectures with this example program:
//
// type T [1<<27 + 1]int64
//
// var d T
// var s []T
//
// func main() {
// s = append(s, d, d, d, d)
// print(len(s), "\n")
// }
if overflow || capmem > maxAlloc { panic(errorString("growslice: len out of range"))
}
var p unsafe.Pointer if et.PtrBytes == 0 {
p = mallocgc(capmem, nil, false)
// The append() that calls growslice is going to overwrite from oldLen to newLen.
// Only clear the part that will not be overwritten.
// The reflect_growslice() that calls growslice will manually clear
// the region not cleared here. 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 lenmem > 0 && writeBarrier.enabled {
// Only shade the pointers in oldPtr since we know the destination slice p
// only contains nil pointers because it has been cleared during alloc. bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-et.Size_+et.PtrBytes)
}
}
memmove(p, oldPtr, lenmem)
return slice{p, newLen, newcap}
}
growslice 函数的参数 oldPtr 是 Slice 的底层数组指针,newLen 是 Slice 的新?度,oldCap 是 Slice 的旧容量,
num 是 Slice 的增??度,et 是 Slice 的元素类型。
扩容的逻辑如下:
- 如果 newLen ?于 0,抛出异常。代码如下:
if newLen < 0 {
panic(errorString("growslice: len out of range"))
}
- 如果 Slice 的元素类型的??为 0,返回?个新的 Slice。代码如下:
if et.Size_ == 0 {
return slice{unsafe.Pointer(&zerobase), newLen, newLen}
}
- 计算新的容量 newcap。代码如下:
newcap := oldCap
doublecap := newcap + newcap if newLen > doublecap {
newcap = newLen
} else {
const threshold = 256 if oldCap < threshold {
newcap = doublecap
} else {
for 0 < newcap && newcap < newLen { newcap += (newcap + 3*threshold) / 4
}
if newcap <= 0 { newcap = newLen
}
}
}
如果 Slice 的容量?于 1024,则新的 Slice 的容量是原来的 2 倍。如果 Slice 的容量?于等于 1024,则新的 Slice
的容量是原来的 1.25 倍,直到新的 Slice 的容量?于等于 newLen。
- 计算新的 Slice 的内存??。代码如下:
var overflow bool
var lenmem, newlenmem, capmem uintptr switch {
case et.Size_ == 1:
lenmem = uintptr(oldLen) newlenmem = uintptr(newLen)
capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem)
case et.Size_ == goarch.PtrSize:
lenmem = uintptr(oldLen) * goarch.PtrSize newlenmem = uintptr(newLen) * goarch.PtrSize
capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize)
case isPowerOfTwo(et.Size_): var shift uintptr
if goarch.PtrSize == 8 {
shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63
} else {
shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31
}
lenmem = uintptr(oldLen) << shift newlenmem = uintptr(newLen) << shift
capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift)
capmem = uintptr(newcap) << shift
default:
lenmem = uintptr(oldLen) * et.Size_ newlenmem = uintptr(newLen) * et.Size_
capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap)) capmem = roundupsize(capmem)
newcap = int(capmem / et.Size_) capmem = uintptr(newcap) * et.Size_
}
- 如果 newcap ?于 maxAlloc,抛出异常。代码如下:
if overflow || capmem > maxAlloc { panic(errorString("growslice: len out of range"))
}
- 分配新的内存。代码如下:
var p unsafe.Pointer if et.PtrBytes == 0 {
p = mallocgc(capmem, nil, false) memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
p = mallocgc(capmem, et, true)
if lenmem > 0 && writeBarrier.enabled { bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-
et.Size_+et.PtrBytes)
}
}
memmove(p, oldPtr, lenmem)
- 返回新的 Slice。代码如下:
return slice{p, newLen, newcap}
总结
Slice 是 Go 语?中的?个重要数据类型,它是对数组的?个抽象,提供了更加强?的功能。Slice 本身并不是?个数组,它指向底层数组。Slice 的功能很强?,可以?便地进?增加、删除、截取等操作。Slice 的扩容是指 Slice的?度超过了 Slice 的容量。当 Slice 的?度超过了 Slice 的容量时,Go 语?会?动扩容 Slice 的容量。
参考阅读
Go 语?设计与实现之 切?
代码如下:
return slice{p, newLen, newcap}
总结
Slice 是 Go 语?中的?个重要数据类型,它是对数组的?个抽象,提供了更加强?的功能。Slice 本身并不是?个数组,它指向底层数组。Slice 的功能很强?,可以?便地进?增加、删除、截取等操作。Slice 的扩容是指 Slice的?度超过了 Slice 的容量。当 Slice 的?度超过了 Slice 的容量时,Go 语?会?动扩容 Slice 的容量。
参考阅读
Go 语?设计与实现之 切?
- 上一篇: php 字符串截取 php如何截取字符串
- 下一篇: 一些强悍的PHP一句话后门 phpinfo一句话
猜你喜欢
- 2024-11-10 PHP数组学习笔记(1) php数组有哪几种类型
- 2024-11-10 Rust语言入门教程 数组和切片 rust语言例子
- 2024-11-10 javascript自学笔记:Array类型1 javascript自学笔记:array类型1怎么解决
- 2024-11-10 Array.from详解: 语法、功能与应用场景
- 2024-11-10 帮你精通JS:解析与盘点数组array的5类22种方法
- 2024-11-10 10 个实用的 JS 技巧 js常用方法大全
- 2024-11-10 WordPress 内置的数组处理相关函数大全
- 2024-11-10 3分钟短文 | PHP获取函数的代码片段,唯有反射最高效
- 2024-11-10 JS 中的类数组对象如何转换为数组?
- 2024-11-10 Go 中的循环是如何转为汇编的?看完你懂了吗?
- 最近发表
- 标签列表
-
- xml (46)
- css animation (57)
- array_slice (60)
- htmlspecialchars (54)
- position: absolute (54)
- datediff函数 (47)
- array_pop (49)
- jsmap (52)
- toggleclass (43)
- console.time (63)
- .sql (41)
- ahref (40)
- js json.parse (59)
- html复选框 (60)
- css 透明 (44)
- css 颜色 (47)
- php replace (41)
- css nth-child (48)
- min-height (40)
- xml schema (44)
- css 最后一个元素 (46)
- location.origin (44)
- table border (49)
- html tr (40)
- video controls (49)