Go的切片类型为处理同类型数据序列提供一个方便而高效的方式。 切片有些相似于其余语言中的数组,可是有一些不一样寻常的特性。git
Go的切片是在数组之上的抽象数据类型,所以在了解切片以前必需要先理解数组。数组
数组类型定义了长度和元素类型。例如, [4]int 类型表示一个四个整数的数组。 数组的长度是固定的,长度是数组类型的一部分( [4]int 和 [5]int 是彻底不一样的类型)。 数组能够以常规的索引方式访问,表达式 s[n] 访问数组的第 n 个元素。数据结构
var a [4]int a[0] = 1 i := a[0] // i == 1
数组不须要显式的初始化;数组的零值是能够直接使用的,数组元素会自动初始化为其对应类型的零值:app
// a[2] == 0, int 类型的零值
类型 [4]int 对应内存中四个连续的整数:函数
Go的数组是值语义。一个数组变量表示整个数组,它不是指向第一个元素的指针
(不像 C 语言的数组)。 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。 (为了不复制数组,你能够传递一个指向数组的指针,可是数组指针并非数组。) 能够将数组看做一个特殊的struct,结构的字段名对应数组的索引,同时成员的数目固定。ui
数组的字面值像这样:3d
b := [2]string{"Penn", "Teller"}
固然,也可让编译器统计数组字面值中元素的数目:指针
b := [...]string{"Penn", "Teller"}
这两种写法, b 都是对应 [2]string 类型。code
数组虽然有适用它们的地方,可是数组不够灵活,所以在Go代码中数组使用的并很少。 可是,切片则使用得至关普遍。切片基于数组构建,可是提供更强的功能和便利。regexp
切片类型的写法是 []T , T 是切片元素的类型。和数组不一样的是,切片类型并无给定固定的长度。
切片的字面值和数组字面值很像,不过切片没有指定元素个数
:
letters := []string{"a", "b", "c", "d"}
切片可使用内置函数 make 建立,函数签名为:
func make([]T, len, cap) []T
其中T表明被建立的切片元素的类型。函数 make 接受一个类型、一个长度和一个可选的容量参数。 调用 make 时,内部会分配一个数组,而后返回数组对应的切片。
var s []byte s = make([]byte, 5, 5) // s == []byte{0, 0, 0, 0, 0}
当容量参数被忽略时,它默认为指定的长度。 下面是简洁的写法:
s := make([]byte, 5)
可使用内置函数 len 和 cap 获取切片的长度和容量信息。
len(s) == 5 cap(s) == 5
切片的零值为 nil 。对于切片的零值, len 和 cap 都将返回0。
切片也能够基于现有的切片或数组生成。 切分的范围由两个由冒号分割的索引对应的半开区间指定。 例如,表达式 b[1:4] 建立的切片引用数组 b 的第1到3个元素空间(对应切片的索引为0到2)。
b := []byte{'g', 'o', 'l', 'a', 'n', 'g'} // b[1:4] == []byte{'o', 'l', 'a'}, sharing the same storage as b
切片的开始和结束的索引都是可选的;它们分别默认为零和数组的长度。
// b[:2] == []byte{'g', 'o'} // b[2:] == []byte{'l', 'a', 'n', 'g'} // b[:] == b
下面语法也是基于数组建立一个切片:
x := [3]string{"Лайка", "Белка", "Стрелка"} s := x[:] // a slice referencing the storage of x
一个切片是一个数组片断的描述。它包含了指向数组的指针,片断的长度和容量(片断的最大长度)。
前面使用 make([]byte, 5) 建立的切片变量 s 的结构以下:
长度是切片引用的元素数目。容量是底层数组的元素数目(从切片指针开始)。 关于长度和容量和区域将在下一个例子说明。
咱们继续对 s 进行切片,观察切片的数据结构和它引用的底层数组:
s = s[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'}
前面建立的切片 s 长度小于它的容量。咱们能够增加切片的长度为它的容量:
s = s[:cap(s)]
切片增加不能超出其容量。增加超出切片容量将会致使运行时异常,就像切片或数组的索引超 出范围引发异常同样。一样,不能使用小于零的索引去访问切片以前的元素。
要增长切片的容量必须建立一个新的、更大容量的切片,而后将原有切片的内容复制到新的切片。 整个技术是一些支持动态数组语言的常见实现。下面的例子将切片 s 容量翻倍,先建立一个2倍 容量的新切片 t ,复制 s 的元素到 t ,而后将 t 赋值给 s :
t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0 for i := range s { t[i] = s[i] } s = t
循环中复制的操做能够由 copy 内置函数替代。copy 函数将源切片的元素复制到目的切片。 它返回复制元素的数目。
func copy(dst, src []T) int
copy 函数支持不一样长度的切片之间的复制(它只复制较短切片的长度个元素)。 此外, copy 函数能够正确处理源和目的切片有重叠的状况。
使用 copy 函数,咱们能够简化上面的代码片断:
t := make([]byte, len(s), (cap(s)+1)*2) copy(t, s) s = t
一个常见的操做是将数据追加到切片的尾部。下面的函数将元素追加到切片尾部, 必要的话会增长切片的容量,最后返回更新的切片:
func AppendByte(slice []byte, data ...byte) []byte { m := len(slice) n := m + len(data) if n > cap(slice) { // if necessary, reallocate // allocate double what's needed, for future growth. newSlice := make([]byte, (n+1)*2) copy(newSlice, slice) slice = newSlice } slice = slice[0:n] copy(slice[m:n], data) return slice }
下面是 AppendByte 的一种用法:
p := []byte{2, 3, 5} p = AppendByte(p, 7, 11, 13) // p == []byte{2, 3, 5, 7, 11, 13}
相似 AppendByte 的函数比较实用,由于它提供了切片容量增加的彻底控制。 根据程序的特色,可能但愿分配较小的活较大的块,或则是超过某个大小再分配。
但大多数程序不须要彻底的控制,所以Go提供了一个内置函数 append , 用于大多数场合;它的函数签名:
func append(s []T, x ...T) []T
append 函数将 x 追加到切片 s 的末尾,而且在必要的时候增长容量。
a := make([]int, 1) // a == []int{0} a = append(a, 1, 2, 3) // a == []int{0, 1, 2, 3}
若是是要将一个切片追加到另外一个切片尾部,须要使用 ... 语法将第2个参数展开为参数列表。
a := []string{"John", "Paul"} b := []string{"George", "Ringo", "Pete"} a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])" // a == []string{"John", "Paul", "George", "Ringo", "Pete"}
因为切片的零值 nil 用起来就像一个长度为零的切片,咱们能够声明一个切片变量而后在循环 中向它追加数据:
// Filter returns a new slice holding only // the elements of s that satisfy f() func Filter(s []int, fn func(int) bool) []int { var p []int // == nil for _, v := range s { if fn(v) { p = append(p, v) } } return p }
正如前面所说,切片操做并不会复制底层的数组。整个数组将被保存在内存中,直到它再也不被引用。 有时候可能会由于一个小的内存引用致使保存全部的数据。
例如, FindDigits 函数加载整个文件到内存,而后搜索第一个连续的数字,最后结果以切片方式返回。
var digitRegexp = regexp.MustCompile("[0-9]+") func FindDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) return digitRegexp.Find(b) }
这段代码的行为和描述相似,返回的 []byte 指向保存整个文件的数组。由于切片引用了原始的数组, 致使 GC 不能释放数组的空间;只用到少数几个字节却致使整个文件的内容都一直保存在内存里。
要修复整个问题,能够将感兴趣的数据复制到一个新的切片中:
func CopyDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) b = digitRegexp.Find(b) c := make([]byte, len(b)) copy(c, b) return c }