切片
slice的特性
又称动态数组,依托数组实现,可以方便地进行扩容和传递,实际使用时比数组更灵活。
type slice struct {
array unsafe.Pointer
len int
cap int
}
以上是go中slice的声明。
初始化
var ints []int
ints := make([]int , 2 ,5)
ps := new([]string)
//注意这里的ps是一个指针array := [5]int{1,2,3,4,5} s1 := array[0:3] s2 := s1[0:1]
切片操作与切片表达式
简单表达式
a[low : hihg]
如果 a 为数组或者切片,则该表达式将切取 a[ low , high )
的元素,如果 a 为string 该表达式将会生成一个string,而不是slice
a := [5]int{1,2,3,4,5}
b := a[1:4]
b[0] = 2
c := b[1:2]
根据之前切片结构的声明,我们知道 slice 有三个元素,对于array(底层数组地址),着重强调,使用简单表达式生成的slice将与原数组或slice共享底层数组,新切片的生成逻辑可以理解为
b.array = &a[low]
b.len = heigh - low
b.cap = len(a) - low //注意 b 的 cap 不是 len(b)
大家可以试试以下代码的输出
func SlicePrint() {
s1 := []int{1, 2}
s2 := s1
s3 := s2[:]
fmt.Printf("%p %p %p\n", &s1[0], &s2[0], &s3[0])
}
func SliceCap() {
var array [10]int{1,2,3,4,5,6,7,8,9}
var slice = array[5:6]
var slice2 = array[9:10]
fmt.Printf("len(slice) = %d\n", len(slice))
fmt.Printf("cap(slice) = %d\n", cap(slice))
fmt.Printf("len(slice) = %d\n", len(slice2))
fmt.Printf("cap(slice) = %d\n", cap(slice2))
}
在上面的例子中如果给slice2添加新元素 slice2 = append(slice2,10)
,原本的底层数组就不够用了,这时go会分配一段的内存空间作为底层数组,并将slice中的元素拷贝到新的数组中然后将新添加的元素加到数组中,而这段新的内存有多大呢,这在一会儿的实现原理中说。
另外,需要注意,如果简单表达式的对象是slice,那么表达式a[low : high]中 low 和 high 的最大值可以是 a 的容量,而不是 a 的长度。
扩展表达式
a[low : high : max]
简单表达是生成的新slice与原数组共享底层数组避免了拷贝元素,节约内存空间的同时可能会带来一定的风险。
新slice (b := a[low:high]
)不仅仅可以读写 a[low] 到 a[high-1] 的元素,而且在使用append(a,x)
添加新的元素还会覆盖掉 a[high]以及后面的元素
a := [5]slice{1,2,3,4,5}
b := a[1:4]
b = append(b,0)
fmt.Println(a[4]) //0
而扩展表达式就是解决这个问题的机制 ,low high max 满足 0 <= low <= high <= max <= cap(a)
max
用于限制新生成切片的容量,新切片的容量为 max - low
array := [10]int
a := array[5:7] //cap = 5
b := array[5:7:7] //cap = 2
slice的实现原理
slice的使用很灵活,但是想要正确使用它,就要了解它的实现原理。
-
var ints []int
slice{array : nil , len : 0 , cap : 0} -
ints := make([]int , 2 ,5)
slice {array : 一段连续内存的起始地址(同时将元素全部初始化为整型的默认值 0 ) , len : 2 , cap : 5}- slice元素的访问
ints := make([]int,2,5) fmt.Pritln(ints[0]) ints = append(ints , 3) // ints 的底层数组变化为 0 0 3 0 0 但只可以访问前三个元素 之后的属于越界访问
- slice元素的访问
-
ps := new([]string)
slice{array : nil , len : 0 , cap : 0}- 这里
ps
就是slice结构体的起始地址,这时slice还没有分配底层数组,如果想要向slice中添加元素需要使用内置函数append()
*ps = append(*ps , "hello世界")
这样 *ps.array 指向的就是一个stringStruct
stringStruct{ str : 底层数组起始地址, len : 11}
- 这里
slice 依托数组实现,底层数组对用户屏蔽吗在底层数组容量不足时可以实现自动分配并生成新的slice
扩容
扩容容量的选择遵守以下规则
- 如果原slice的容量翻倍后仍然小于最低容量需求 cap ,就直接扩容到 cap
- 否则,如果原slice的容量小于1024,则新slice的容量将扩大为原来的2倍
- 如果原slice的容量大于等于1024,则新slice的容量将扩大为原来的1.25倍
src/runtime/slice.go:growslice
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 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
}
}
}
在此规则之上,还会考虑元素类型与内存分配规则,对实际扩张值做一些微调。比如,os常常将内存切分为 64、80、96、112 等常用的大小 go的内存分配机制会根据预估大小匹配合适的内存块分配给新slice。