本文共 1962 字,大约阅读时间需要 6 分钟。
Go语言的切片实现可以看runtime的slice.go文件,切片是Go语言内置的数据结构,编译器识别到切片语法操作时会自动调用runtime对应底层实现,所以用起来就非常方便,这也是语言级的实现比库实现的优势。
可以通过一个例子来看切片的内存模型:// go version: 1.7package mainimport ( "fmt" "reflect" "unsafe")// GDB时,避免局部变量可能会被优化,全局变量方便GDB查看进程对应内存// 编译时加调试信息并去掉优化: go build -gcflags "-N -l" hi.govar ( a = [...]int{ 1, 2, 3, 4, 5} // 数组 b = a[2:4] // 切片 --> 通过数组+[:]语法生成切片 c = &a // 指针指向数组 d = []int{ 1, 2, 3} // 切片 --> 直接初始化切片[]Type{data..}语法)// runtime: 拷贝自slice.go的私有结构体, 64位操作系统就是24个字节大小type slice struct { array unsafe.Pointer len int cap int}func main() { fmt.Println("&a[2]", &a[2]) // &a[2] 0xf8270 fmt.Println("len", len(b), "cap", cap(b)) // len 2 cap 3 fmt.Println(reflect.TypeOf(a), unsafe.Sizeof(a)) // [5]int 40 fmt.Println(reflect.TypeOf(b), unsafe.Sizeof(b)) // []int 24 fmt.Println(reflect.TypeOf(c), unsafe.Sizeof(c)) // *[5]int 8 fmt.Printf("%+v\n", *(*slice)(unsafe.Pointer(&b))) // {array:0xf8270 len:2 cap:3} fmt.Printf("%+v\n", *(*slice)(unsafe.Pointer(&d))) // {array:0xf8090 len:3 cap:3} // append 出发自动扩容,生成一个新的切片实例 fmt.Println(unsafe.Pointer(&d)) // 0xfa100 d = append(d, 4) fmt.Printf("%+v\n", *(*slice)(unsafe.Pointer(&d))) // {array:0xc4200121e0 len:4 cap:6} fmt.Println(unsafe.Pointer(&d)) // 0xfa100}
内置函数 append 会出发切片的扩容,这个跟C++ vector实现原理有点类似,申请个新的底层数组,拷贝原有数据过去。
// 切片的append操作,返回一个新切片slice = append(slice, elem1, elem2)slice = append(slice, anotherSlice...)// runtime src: slice.go, @fun: growslice// 扩容策略: 小于1024,每次扩容*2; 大于1024,每次扩容*1.25newcap := old.capdoublecap := newcap + newcapif cap > doublecap { newcap = cap} else { if old.len < 1024 { newcap = doublecap } else { for newcap < cap { newcap += newcap / 4 } }}
切片使用起来非常的方便,这都得益于go语言语法级对切片的支持,很容用[:]之类的写法来生成切片实例。还有go的协程也是类似,而库级别的实现远远没有语法级别的支持用起来方便,这都是go的优势所在。
转载地址:http://qbqli.baihongyu.com/