数组
- 固定长度的特定类型元素组成的序列,与c语言区别,golang中的数组是值类型的.
func main () {
arr := [2]int{1, 2}
passArr(arr)
fmt.Println(arr[0], " ", arr[1])
}
func passArr(arr [2]int) {
arr[0] = 10
fmt.Println(arr[0], " ", arr[1])
}
-
值传递,在子方法中修改数组,不会影响原数组的数据。copy副本的方式,耗费大量空间
-
使用指针可以减少空间的浪费,但不安全(在子方法中更改指针指向)。
func main () {
arr := [2]int{1, 2}
passArr(&arr)
fmt.Println(arr[0], " ", arr[1])
}
func passArr(arr *[2]int) {
arr[0] = 10
arr2 := [2]int{100, 200}
arr = &arr2
fmt.Println(arr[0], " ", arr[1])
}
使用切片可以避免上述问题,切片是引用传递更具有效率,但依旧又反例:
func Array() (x [1024]int) {
for i := 0; i < 1024; i++ {
x[i] = i
}
return x
}
func Slice() (y []int) {
y = make([]int, 1024)
for i :=0; i < 1024; i++ {
y[i] = i
}
return y
}
func BenchmarkArray(b *testing.B) {
for i := 0; i < b.N; i++ {
Array()
}
}
func BenchmarkSlice(b *testing.B) {
for y := 0; y < b.N; y++ {
Slice()
}
}
执行: go test -bench . -benchmem -gcflags “-N -l”
BenchmarkArray-8 364608 3747 ns/op 0 B/op 0 allocs/op
BenchmarkSlice-8 156194 6612 ns/op 8192 B/op 1 allocs/op
从benchmark结果开出来:在8核上,数组循环364608次,平均每次执行时间为3747,堆上内存分配与分配次数均为0;而切片则差写,循环156194次,平均执行时间为6612(近乎两倍),对上分配总内存8192,分配次数也是1
- 如此,并非所有的时候都适合用切片替换数组,因为切片可能会在堆上分配内存,而且小数组在栈上copy也未必消耗比make大
切片
1. 数据结构
slice代表变长的序列,一个slice由三部分组成:指针、长度、容量,指针指向切片的第一个元素(存储底层数组中的某个元素的地址),长度对应slice的数目,容量是slice的最大长度(len函数),超过了需要动态扩容(cap函数)
2. slice作为参数真的是引用传递吗
func main() {
arr := []int{1, 2}
fmt.Printf("%p,\n", &arr)
passArr(arr)
}
func passArr(arr []int) {
fmt.Printf("%p,\n", &arr)
}
输出
0xc0000044a0,
0xc0000044e0,
- 明显地址不同,能达到引用传递的效果是因为复制了底层数组的指针
- golang没有引用传递(或者是同于c/c++一样的引用传递)
3. 切片比较?
slice can only be compared to nil
4. slice扩容原则
上源码
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}
}
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
// 小于1024,*2扩容
if old.len < 1024 {
newcap = doublecap
} else {
// 大于1024,*1.25
for newcap < cap {
newcap += newcap / 4
}
}
}
// 下面代码省略
....
}
- 小于1024,每次扩容后的cap = oldCap * 2
- 大于1024,每次扩容cap = oldCap * 1.25
注意:slice的扩容会涉及到数组的copy,然后产生新的数组,slice也指向新的数组,会给系统带来额外的开销。