扩展切片
-
append
在切片开头添加元素,append
的本质是用于追加元素而不是扩展容量, 扩展切片的容量只是append
的一个副作用var a = []int{1,2,3} a = append([]int{0}, a...)
在开头一般都会导致内存的重新分配,而且会导致已有的元素全部复制 1 次
因此,从切片的开头添加元素的性能一般要比从尾部追加元素的性能差很多 -
append
函数返回新的切片, 也就是说它支持链式操作var a []int // 在 a[i] 的位置添加一个元素 原先后面的元素往后移 a = append(a[:i], append([]int{x}, a[i:]...)...)
append 调用都会创建一个临时切片, 可以用 copy 和 append 组合可以避免创建
中间的临时切片, 同样是完成添加元素的操作a = append(a, 0) // 切片扩展一个空间 copy(a[i+1:], a[i:]) // a[i:] 向后移动 1 个位置 a[i] = x // 设置新添加的元素
删除切片元素
- 删除元素的位置有三种情况: 从开头位置删除, 从中间位置删除, 从尾部删除。 其中删除切片尾部的元素最快:
- 删除尾部元素
a := []int{1,2,3} a = a[:len(a)-1] // 删除尾部 1 个元素 a = a[:len(a)-N] // 删除尾部 N 个元素
- 删除开头可以直接移动数据指针
a = a[1:] a = a[N:] // 也可以不移动指针 用 append 在原地完成 // 所谓的原地完成是指在原有的切片数据对应的内存区间内完成, 不会导致内存空间结构的变化 a = append(a[:0], a[1:]...) a = append(a[:0], a[N:]...) // 也可以用 copy 完成删除开头的元素 // copy函数把后面的几个元素覆盖前面的元素 a = a[:copy(a, a[1:])]
- 删除中间元素: 需要对剩余的元素进行一次整体挪动, 同样可以用 append 或 copy 原地完成:
a = append(a[:i], a[i+1:]...) a = a[:i+copy(a[i:], a[i+1:])]
- 删除尾部元素
切片内存技巧
对于切片来说, len 为 0,但是 cap 不为 0 的切片则是非常有用的特性。
-
下面函数利用了 0长切片的特性, 实现高效而且简洁
func TrimSpace(s []byte) []byte { b := s[:0] for _, x := range s { if x != ' ' { b = append(b,x) } } }
-
切片高效操作的要点是要降低内存分配的次数, 尽量保证
append
操作不会超出cap
的容量, 降低触发内存分配的次数和每次分配内存的大小 -
切片内存泄漏: 切片操作并不会复制底层的数据. 底层的数组会被保存在内存中, 直到它不再被引用
但有时候可能会因为一个小的内存引用而导致底层整个数组处于被使用的状态, 这会延迟自动内存回收器对底层数组的回收func FindPhoneNumber(filename string) []byte { b, _ ;= ioutil.ReadFile(filename) return regexp.MustCompile(`[0-9]+`).Find(b) }
这段代码返回的 []byte 指向保存整个文件的数组。 因为切片引用了整个原始数组, 导致自动垃圾回收器不能及时释放底层数组的空间
一个小的需求可能导致需要长时间保存整个文件数据。虽然这并不是传统意义上的内存泄漏,但是可能会拖慢系统的整体性能
修复这个问题 可以将感兴趣的数据复制到一个新的切片中(虽然传值有一定的代价, 但是换取的好处是切断了对原始数据的依赖)return append([]byte{}, b...)