来源:自学PHP网 时间:2019-08-07 16:47 作者:小飞侠 阅读:次
[导读] 浅谈golang slice 切片原理...
slice介绍 数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫slice,slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度,它是可变长的,可以随时往slice里面加数据。 初看起来,数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是个指针。数组切片的数据结构可以抽象为以下3个变量: 1.一个指向原生数组的指针(point):指向数组中slice指定的开始位置; 从底层实现的角度来看,数组切片实际上仍然使用数组来管理元素,基于数组,数组切片添加了一系列管理功能,可以随时动态扩充存放空间,并且可以被随意传递而不会导致所管理的元素被重复复制。 slice声明 声明slice时方括号[]内没有任何数据 golang 中的 slice 非常强大,让数组操作非常方便高效。在开发中不定长度表示的数组全部都是 slice 。但是很多同学对 slice 的模糊认识,造成认为golang中的数组是引用类型,结果就是在实际开发中碰到很多坑,以至于出现一些莫名奇妙的问题,数组中的数据丢失了。 下面我们就开始详细理解下 slice ,理解后会对开发出高效的程序非常有帮助。 这个是 slice 的数据结构,它很简单,一个指向真实 array 地址的指针 ptr ,slice 的长度 len 和容量 cap 。 其中 len 和 cap 就是我们在调用 len(slice) 和 cap(slice) 返回的值。 我们来按照 slice 的数据结构定义来解析出 ptr, len, cap // 按照上图定义的数据结构 type Slice struct { ptr unsafe.Pointer // Array pointer len int // slice length cap int // slice capacity } 下面写一个完整的程序,尝试把golang中slice的内存区域转换成我们定义的 Slice 进行解析 package main import ( "fmt" "unsafe" ) // 按照上图定义的数据结构 type Slice struct { ptr unsafe.Pointer // Array pointer len int // slice length cap int // slice capacity } // 因为需要指针计算,所以需要获取int的长度 // 32位 int length = 4 // 64位 int length = 8 var intLen = int(unsafe.Sizeof(int(0))) func main() { s := make([]int, 10, 20) // 利用指针读取 slice memory 的数据 if intLen == 4 { // 32位 m := *(*[4 + 4*2]byte)(unsafe.Pointer(&s)) fmt.Println("slice memory:", m) } else { // 64 位 m := *(*[8 + 8*2]byte)(unsafe.Pointer(&s)) fmt.Println("slice memory:", m) } // 把slice转换成自定义的 Slice struct slice := (*Slice)(unsafe.Pointer(&s)) fmt.Println("slice struct:", slice) fmt.Printf("ptr:%v len:%v cap:%v \n", slice.ptr, slice.len, slice.cap) fmt.Printf("golang slice len:%v cap:%v \n", len(s), cap(s)) s[0] = 0 s[1] = 1 s[2] = 2 // 转成数组输出 arr := *(*[3]int)(unsafe.Pointer(slice.ptr)) fmt.Println("array values:", arr) // 修改 slice 的 len slice.len = 15 fmt.Println("Slice len: ", slice.len) fmt.Println("golang slice len: ", len(s)) } 运行一下查看结果 $ go run slice.go slice memory: [0 64 6 32 200 0 0 0 10 0 0 0 0 0 0 0 20 0 0 0 0 0 0 0] slice struct: &{0xc820064000 10 20} ptr:0xc820064000 len:10 cap:20 golang slice len:10 cap:20 array values: [0 1 2] Slice len: 15 golang slice len: 15 看到了,golang slice 的memory内容,和自定义的 Slice 的值,还有按照 slice 中的指针指向的内存,就是实际 Array 数据。当修改了 slice 中的len, len(s) 也变了。 接下来结合几个例子,了解下slice一些用法 声明一个Array通常使用 make ,可以传入2个参数,也可传入3个参数,第一个是数据类型,第二个是 len ,第三个是 cap 。如果不穿入第三个参数,则 cap=len ,append 可以用来向数组末尾追加数据。 这是一个 append 的测试 // 每次cap改变,指向array的ptr就会变化一次 s := make([]int, 1) fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s))) for i := 0; i < 5; i++ { s = append(s, i) fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s))) } fmt.Println("Array:", s) 运行结果 len:1 cap: 1 array ptr: 0xc8200640f0 len:2 cap: 2 array ptr: 0xc820064110 len:3 cap: 4 array ptr: 0xc8200680c0 len:4 cap: 4 array ptr: 0xc8200680c0 len:5 cap: 8 array ptr: 0xc82006c080 len:6 cap: 8 array ptr: 0xc82006c080 Array: [0 0 1 2 3 4] 看出来了吧,每次cap改变的时候指向array内存的指针都在变化。当在使用 append 的时候,如果 cap==len 了这个时候就会新开辟一块更大内存,然后把之前的数据复制过去。 实际go在append的时候放大cap是有规律的。在 cap 小于1024的情况下是每次扩大到 2 * cap ,当大于1024之后就每次扩大到 1.25 * cap 。所以上面的测试中cap变化是 1, 2, 4, 8 在实际使用中,我们最好事先预期好一个cap,这样在使用append的时候可以避免反复重新分配内存复制之前的数据,减少不必要的性能消耗。 创建切片 s := []int{1, 2, 3, 4, 5} fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s))) fmt.Println("Array:", s) s1 := s[1:3] fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s1), cap(s1), *(*unsafe.Pointer)(unsafe.Pointer(&s1))) fmt.Println("Array", s1) 运行结果 len:5 cap: 5 array ptr: 0xc820012210 Array: [1 2 3 4 5] len:2 cap: 4 array ptr: 0xc820012218 Array [2 3] 在一个切片基础上创建新的切片 s1 ,新切片的 ptr 指向的就是 s1[0] 数据的内存地址。可以看到指针地址 0xc820012210 与 0xc820012218 相差 8byte 正好是一个int类型长度,cap也相应的变为4 就写到这里了,总结一下,切片的结构是指向数据的指针,长度和容量。复制切片,或者在切片上创建新切片,切片中的指针都指向相同的数据内存区域。 知道了切片原理就可以在开发中避免出现错误了,希望这篇博客可以给大家带来帮助。也希望大家多多支持自学php网。 |
自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习
京ICP备14009008号-1@版权所有www.zixuephp.com
网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com