slice(切片)是建立在数组之上的一个抽象类型。
一、切片的底层
它是一个标准库中定义的结构体,其定义如下:
type slice struct {
// 底层数组指针(或者说是指向一块连续内存空间的起点)
array unsafe.Pointer
// 长度 len int
// 容量 cap int
}
切片扩容
1、如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍;
2、如果原Slice容量大于等于1024,则新Slice容量将扩大为原来的1.25倍;
3、如果扩容后的大小仍不能满足,那么直接扩容到所需的容量
4、最终还需要按照go内存管理的级别去对齐内存
二、切片相关操作
1、新增元素(append)
问题:append操作得到新的slice与原来的 slice是什么关系?
如果append导致slice发生扩容,则新的slice和原来的slice指向不同的内存;
如果append没有导致slice发生扩容,则新的slice和原来的slice指向同一块内存;
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
s1 := make([]int, 2)
s2 := append(s1, 3)
PrintSliceStruct(&s1)
PrintSliceStruct(&s2)
s3 := append(s2, 4)
PrintSliceStruct(&s3)
}
func PrintSliceStruct(s *[]int) {
ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}
输出如下:
slice struct: &{Data:824634794160 Len:2 Cap:2}, slice is &[0 0]
slice struct: &{Data:824634843200 Len:3 Cap:4}, slice is &[0 0 3]
slice struct: &{Data:824634843200 Len:4 Cap:4}, slice is &[0 0 3 4]
2、截取元素
问题:截取操作得到新的slice与原来的 slice是什么关系?
截取操作得到的新的slice与原来的slice指向同一块内存
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
s := make([]int, 5)
s1 := s[1:]
PrintSliceStruct(&s1)
s2 := s[1:3]
PrintSliceStruct(&s2)
s3 := s[len(s)-1:]
PrintSliceStruct(&s3)
s4 := s[2:]
PrintSliceStruct(&s4)
}
func PrintSliceStruct(s *[]int) {
ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}
输出如下:
slice struct: &{Data:824633770712 Len:4 Cap:4}, slice is &[0 0 0 0]
slice struct: &{Data:824633770712 Len:2 Cap:4}, slice is &[0 0]
slice struct: &{Data:824633770736 Len:1 Cap:1}, slice is &[0]
slice struct: &{Data:824633770720 Len:3 Cap:3}, slice is &[0 0 0]
3、删除元素
问题:删除操作得到新的slice与原来的 slice是什么关系?
删除操作一般是通过append来完成的,请看以下案例:
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
s := []int{1, 2, 3, 4, 5}
_ = s[4]
PrintSliceStruct(&s)
s1 := append(s[:1], s[2:]...)
PrintSliceStruct(&s1)
PrintSliceStruct(&s)
_ = s[4]
// 访问s1[4]会报错,因为s1的长度为4
// _ = s1[4]
}
func PrintSliceStruct(s *[]int) {
ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}
输出如下:
slice struct: &{Data:824633770704 Len:5 Cap:5}, slice is &[1 2 3 4 5]
slice struct: &{Data:824633770704 Len:4 Cap:5}, slice is &[1 3 4 5]
slice struct: &{Data:824633770704 Len:5 Cap:5}, slice is &[1 3 4 5 5]
可以看到:
s和s1指向的是同一块内存
s1的长度变成了4,但s1的容量仍然是5,因为s[:1]的容量是5
4、深度拷贝
问题:深度拷贝得到的slice和原来的slice是什么关系?
深度拷贝得到的slice会把底层的数组复制,新的slice和旧的slice指向不同的内存
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1)
PrintSliceStruct(&s1)
PrintSliceStruct(&s2)
}
func PrintSliceStruct(s *[]int) {
ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}
输出如下:
slice struct: &{Data:824633794752 Len:3 Cap:3}, slice is &[1 2 3]
slice struct: &{Data:824633794776 Len:3 Cap:3}, slice is &[1 2 3]
三、一些问题
1、切片通过函数,传的是什么?
传递的是slice结构体的拷贝
2、在函数内层改变切片,函数外层会受影响吗?
受不受影响,主要要看函数内层和函数外层的slice里面的指针指向的是否是同一块内存。如果切片在函数内层没有做扩容,那么指向就是原来的同一块内存,如果做了扩容,那么将指向不同的内存。
函数内层截取元素或者删除元素,对于函数外层是有影响的,因为截取或者删除,操作的和函数外层是同一块内存。
四、一道面试真题
package main
import (
"fmt"
)
func main() {
doAppend := func(s []int) {
s = append(s, 1)
printLengthAndCapacity(s)
}
s := make([]int, 8, 8)
doAppend(s[:4])
printLengthAndCapacity(s)
doAppend(s)
printLengthAndCapacity(s)
}
func printLengthAndCapacity(s []int) {
fmt.Println(s)
fmt.Printf("len=%d, cap=%d\n", len(s), cap(s))
}
输出结果如下:
[0 0 0 0 1]
len=5, cap=8
[0 0 0 0 1 0 0 0]
len=8, cap=8
[0 0 0 0 1 0 0 0 1]
len=9, cap=16
[0 0 0 0 1 0 0 0]
len=8, cap=8