golang 数据二 (切片)

100018 · · 1196 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在项目开发过程中,更多的场景是需要一个长度可以动态更新的数据存储结构,切片本身并非是动态数组或数组指针,他内部通过指针引用底层数组,并设定相关属性将数据读写操作限定在指定区域内。比如:

/runtime/slice.go

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

切片初始化

切片有两种基本初始化方式:

切片可以通过内置的make函数来初始化,初始化时len=cap,一般使用时省略cap参数,默认和len参数相同,在追加元素时,如果容量cap不足时,将按len的2倍动态扩容。

通过数组来初始化切片,以开始和结束索引位置来确定最终所引用的数组片段。

//make([]T,聽len,聽cap)聽//T是切片的数据的类型,len表示length,cap表示capacity
{
聽聽聽聽s聽:=聽make([]int,5)聽聽聽聽聽聽//len:聽5聽聽cap:聽5
聽聽聽聽s聽:=聽make([]int,5,10)聽聽聽聽//len:聽5聽聽cap:聽10
聽聽聽聽s聽:=聽[]int{1,2,3}
}聽

{
聽聽聽聽arr聽:=聽[...]int{0,1,2,3,4,5,6,7,8,9}
聽聽聽聽s1聽:=聽arr[:]
聽聽聽聽s2聽:=聽arr[2:5]
聽聽聽聽s3聽:=聽arr[2:5:7]
聽聽聽聽s4聽:=聽arr[4:]
聽聽聽聽s5聽:=聽arr[:4]
聽聽聽聽s6聽:=聽arr[:4:6]

聽聽聽聽fmt.Println("s1:聽",s1,聽len(s1),cap(s1))
聽聽聽聽fmt.Println("s2:聽",s2,聽len(s2),cap(s2))
聽聽聽聽fmt.Println("s3:聽",s3,聽len(s3),cap(s3))
聽聽聽聽fmt.Println("s4:聽",s4,聽len(s4),cap(s4))
聽聽聽聽fmt.Println("s5:聽",s5,聽len(s5),cap(s5))
聽聽聽聽fmt.Println("s6:聽",s6,聽len(s6),cap(s6))
}
输出:
s1:聽聽[0聽1聽2聽3聽4聽5聽6聽7聽8聽9]聽10聽10
s2:聽聽[2聽3聽4]聽3聽8
s3:聽聽[2聽3聽4]聽3聽5
s4:聽聽[4聽5聽6聽7聽8聽9]聽6聽6
s5:聽聽[0聽1聽2聽3]聽4聽10
s6:聽聽[0聽1聽2聽3]聽4聽6

通过上例说明cap 是表示切片所引用数组片段的真实长度,len是表示已经赋过值的最大下标(索引)值加1.

注意下面两种初始化方式的区别:

{
聽聽聽聽var聽a聽聽[]int
聽聽聽聽b聽:=聽[]int{}
聽聽聽聽fmt.Println(a==nil,b==nil)

聽聽聽聽fmt.Printf("a:聽%#v\n",聽(*reflect.SliceHeader)(unsafe.Pointer(&a)))
聽聽聽聽fmt.Printf("b:聽%#v\n",聽(*reflect.SliceHeader)(unsafe.Pointer(&b)))
聽聽聽聽fmt.Printf("a聽size聽%d\n",聽unsafe.Sizeof(a))
聽聽聽聽fmt.Printf("b聽size聽%d\n",聽unsafe.Sizeof(b))
}
输出:
true聽false
a:聽&reflect.SliceHeader{Data:0x0,聽Len:0,聽Cap:0}
b:聽&reflect.SliceHeader{Data:0x5168b0,聽Len:0,聽Cap:0}
a聽size聽24
b聽size聽24
说明:
1.聽变量b的内部指针被赋值,即使该指针指向了runtime.zerobase,但它依然完成了初始化操作
2.聽变量a表示一个未初始化的切片对象,切片本身依然会分配所需的内存

切片之间不支持逻辑运算符,仅能判断是否为nil,比如:

{
聽聽聽聽var聽a聽聽[]int
聽聽聽聽b聽:=聽[]int{}
聽聽聽聽fmt.Println(a==b)聽//invalid聽operation:聽a聽==聽b聽(slice聽can聽only聽be聽compared聽to聽nil)
}

reslice

在原slice的基础上进行新建slice,新建的slice依旧指向原底层数组,新创建的slice不能超出原slice

的容量,但是不受其长度限制,并且如果修改新建slice的值,对所有关联的切片都有影响,比如:

{
聽聽聽聽s聽:=聽[]string{"a","b","c","d","e","f","g"}

聽聽聽聽s1聽:=聽s[1:3]聽聽聽聽聽聽聽聽//b,c
聽聽聽聽fmt.Println(s1,聽len(s1),cap(s1))
聽聽聽聽s1_1聽:=聽s1[2:5]聽聽聽聽聽聽聽聽//c,d,e
聽聽聽聽fmt.Println(s1_1,聽len(s1_1),cap(s1_1))
}
输出:
[b聽c]聽2聽6
[d聽e聽f]聽3聽4

append

向切片尾部追加数据,返回新的切片对象; 数据被追加到原底层数组,如果超出cap限制,则为新切片对象重新分配数组,新分配的数组cap是原数组cap的2倍,比如:

{
聽聽聽聽s聽:=聽make([]int,0,5)
聽聽聽聽s聽=聽append(s聽,聽1)
聽聽聽聽s聽=聽append(s聽,聽2,3,4,5)
聽聽聽聽fmt.Printf("%p,聽%v,聽%d\n",聽s,s,聽cap(s))
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽
聽聽聽聽s聽=聽append(s聽,聽6)聽聽聽聽聽聽聽//重新分配内存
聽聽聽聽fmt.Printf("%p,聽%v,聽%d\n",聽s,聽s,聽cap(s))
}
输出:
0xc420010210,聽[1聽2聽3聽4聽5],聽5
0xc4200140a0,聽[1聽2聽3聽4聽5聽6],聽10

如果是向nil切片追加数据,则会高频率的重新分配内存和数据复制,比如:

{
聽聽聽聽var聽s聽[]int
聽聽聽聽fmt.Printf("%p,聽%v,聽%d\n",聽s,s,聽cap(s))
聽聽聽聽for聽i:=聽0;聽i聽<聽10;i++{
聽聽聽聽聽聽聽聽s聽=聽append(s,聽i)
聽聽聽聽聽聽聽聽fmt.Printf("%p,聽%v,聽%d\n",聽s,聽s,聽cap(s))
聽聽聽聽}
}

所以为了避免程序运行中的频繁的资源开销,在某些场景下建议预留出足够多的空间。

copy

两个slice之间复制数据时,允许指向同一个底层数组,并允许目标区间重叠。最终复制的长度以较短的切片长度(len)为准,比如:

{
聽聽聽聽s1聽:=聽[]int{0,聽1,聽2,聽3,聽4,聽5聽,6}
聽聽聽聽s2聽:=聽[]int{7,聽8聽,9}
聽聽聽聽copy(s1,s2)
聽聽聽聽fmt.Println(s1,len(s1),cap(s1))
聽聽聽聽s1聽=聽[]int{0,聽1,聽2,聽3,聽4,聽5聽,6}
聽聽聽聽s2聽=聽[]int{7,聽8聽,9}聽
聽聽聽聽copy(s2,s1)
聽聽聽聽fmt.Println(s2,len(s2),cap(s2))
}

那么可不可以在同一切片之间复制数据呢?

在项目开发过程中,如果slice长时间引用一个大数组中很小的片段,那么建议新建一个独立的切片,并复制出所需的数据,以便原数组内存可以被gc及时释优化回收。

本文出自 “博学于文,约之于礼” 博客,转载请与作者联系!


有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:100018

查看原文:golang 数据二 (切片)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1196 次点击  
加入收藏 微博
下一篇:Golang的 signal
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传