Go Slices: 切片的使用和原理
简介
Go的slice类型提供了一种方便有效的方式来处理键入数据序列。 切片类似于其他语言中的数组,但具有一些不同寻常的属性。 本文将研究什么是切片以及如何使用它们。
数组Array
Go语言中切片是对数组的封装和抽象,所以再理解slice之前必须先理解数组类型。
数组类型定义需要指定长度和数据类型,有趣的是,Go语言中[3]int跟[4]int并不相同
var temp [4]int
Go语言中数组是值类型。 数组变量表示整个数组; 它不是指向第一个数组元素的指针(就像C语言中那样)。 这意味着分配或传递数组值时,只会复制其内容。 为避免复制,可以将指针传递给数组,但这是指向数组的指针而不是数组。
创建数组共有两种方式
arr := [3]int{1, 2, 3}arr := [...]int{1, 2, 3}//数组长度会自动推导出来
切片(slices)
但是在Go中,我们几乎很难用到数组,因为数组扩展困难,而且Go语言的切片基于数组提供了很多强大的功能。
Go的类型规范是 []T,你可以这样声明一个切片
slices := []int{1, 2, 3, 4, 5}
也可以使用名为make的函数创建切片
//T为创建切片的数据类型,len为长度,cap为可选容量func make([]T, len, cap) []T
具体创建方式如下:
var slices []intslices := make(a, 5, 5)
切片也可以由现有切片生成
// temp则为切片slices下标为m到n之间的切片// 如果省略m或者n,则m和n的默认值为0和该切片的长度temp := slices[m:n]
切片的原理
切片由三部分组成,一个指向数组的指针,一个表示长度的int类型,一个表示容量的int类型
当使用var a []int, a := make(a, 5)创建一个具体切片时,数据结构图如下:
长度是切片所引用的元素数。 容量是基础数组中元素的数量(从切片指针所指的元素开始)。 在接下来的几个示例中,将明确长度和容量之间的区别。
在对s进行切片时,观察slice数据结构中的变化及其与基础数组的关系:
a = a[2:4]
切片不会复制切片的数据。 它创建一个指向原始数组的新切片值。 这使切片操作与操作数组索引一样有效。 因此,修改重新切片的元素(而非切片本身)将修改原始切片的元素:
d := []byte{'r', 'o', 'a', 'd'}e := d[2:]// e == []byte{'a', 'd'}e[1] = 'm'// e == []byte{'a', 'm'}// d == []byte{'r', 'o', 'a', 'm'}
使用切片不能切0以下,切片长度以上,否则会导致异常
切片扩容(append和copy)
要增加切片的容量,必须创建一个更大的新切片,然后将原始切片的内容复制到其中。 这种技术是来自其他语言的动态数组实现在后台工作的方式。 下一个示例通过制作一个新的切片t,将s的内容复制到t,然后将切片值t分配给s,使s的容量增加一倍:
t := make([]byte, len(s), (cap(s)+1)*2) // +1 是考虑到s容量为0的情况下for i := range s {t[i] = s[i]}s = t
在Go语言中,也有类似复制功能的函数,返回值为复制的元素数
func copy(dst, src []T) int
利用copy函数,则我们可以简化代码为
t := make([]byte, len(s), (cap(s)+1)*2)copy(t, s)s = t
在其他语言中扩容的常规操作都是扩大一倍容量,在数组末尾依次拷贝新值。不妨使用go语言实现
func AppendByte(slice []byte, data ...byte) []byte {m := len(slice)n := m + len(data)if n > cap(slice) {// 2倍扩容newSlice := make([]byte, (n+1)*2)copy(newSlice, slice)slice = newSlice}slice = slice[0:n]copy(slice[m:n], data)return slice}
当然go语言也提供了append函数实现上述代码的功能,也便于对数组动态的修改
func append(s []T, x ...T) []T
使用案例如下:
a := make([]int, 1)// a == []int{0}a = append(a, 1, 2, 3)// a == []int{0, 1, 2, 3}
注意事项
如前所述,对切片进行重新切片不会复制基础数组。 完整的数组将保留在内存中,直到不再被引用为止。 有时,这可能导致程序仅需要一小部分数据时就将所有数据保存在内存中。
例如,此FindDigits函数将文件加载到内存中,并在文件中搜索第一组连续的数字,并将它们作为新切片返回。
var digitRegexp = regexp.MustCompile("[0-9]+")func FindDigits(filename string) []byte {b, _ := ioutil.ReadFile(filename)return digitRegexp.Find(b)}
由于切片引用了原始数组,因此只要将切片保留在垃圾收集器周围,就无法释放该数组。要解决此问题,可以在返回之前将所需要的数据复制到新的切片中:
func CopyDigits(filename string) []byte {b, _ := ioutil.ReadFile(filename)b = digitRegexp.Find(b)c := make([]byte, len(b))copy(c, b)return c}
如果觉得《golang slices使用和原理》对你有帮助,请点赞、收藏,并留下你的观点哦!