设为首页 收藏本站
查看: 1756|回复: 0

[经验分享] golang slice 切片原理

[复制链接]

尚未签到

发表于 2018-9-19 12:09:58 | 显示全部楼层 |阅读模式
  golang 中的 slice 非常强大,让数组操作非常方便高效。在开发中不定长度表示的数组全部都是 slice 。但是很多同学对 slice 的模糊认识,造成认为golang中的数组是引用类型,结果就是在实际开发中碰到很多坑,以至于出现一些莫名奇妙的问题,数组中的数据丢失了。
  下面我们就开始详细理解下 slice ,理解后会对开发出高效的程序非常有帮助。
  这个是 slice 的数据结构,它很简单,一个指向真实 array 地址的指针 ptrslice 的长度 len 和容量 cap
DSC0000.png

  
DSC0001.png
  其中 lencap 就是我们在调用 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=lenappend 可以用来向数组末尾追加数据。
  这是一个 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(&quot;len:%d cap: %d array ptr: %v \n&quot;, len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
  
}
  

  
fmt.Println(&quot;Array:&quot;, 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(&quot;len:%d cap: %d array ptr: %v \n&quot;, len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
  
fmt.Println(&quot;Array:&quot;, s)
  

  
s1 := s[1:3]
  
fmt.Printf(&quot;len:%d cap: %d array ptr: %v \n&quot;, len(s1), cap(s1), *(*unsafe.Pointer)(unsafe.Pointer(&s1)))
  
fmt.Println(&quot;Array&quot;, 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] 数据的内存地址。可以看到指针地址 0xc8200122100xc820012218 相差 8byte 正好是一个int类型长度,cap也相应的变为4
  就写到这里了,总结一下,切片的结构是指向数据的指针,长度和容量。复制切片,或者在切片上创建新切片,切片中的指针都指向相同的数据内存区域。
  知道了切片原理就可以在开发中避免出现错误了,希望这篇博客可以给大家带来帮助。
  参考:https://blog.golang.org/go-slices-usage-and-internals
  附上 go 源码中 slice 的数据结构定义
  

type slice struct {  array unsafe.Pointer
  len   int
  cap   int
  
}
  




运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-594248-1-1.html 上篇帖子: Golang学习 - reflect 包 下篇帖子: golang的并发不等于并行
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表