go语言的Slice-补充
前言
上次我们说了下这个切片的一些底层,后来我发现有的地方还没说到,所以在这里补充说明一下
slice的内存泄露
前面我们说过,切片的操作是不会复制底层的数据的,底层的数组会被保留在内存中直到它不在被引用,这时它才会被
GC
回收(GC
的内容比较难,小弟也没搞懂,后面研究完了再来分享一下,这里只需要知道这段不再被使用的内存会被释放掉就行);但是有时候你的代码编写如果没注意到这点就可能导致整个数组处于被使用的状态,导致延迟了GC
对底层数组的回收,我们下面先来看个例子:
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return regexp.MustCompile("[0-9]+").Find(b)
}
// 这个方法是在一个文件中读取整个文件数据,然后保存到b中(这是个[]byte),然后以切片的形式返回收缩到的第一个出现的电话号码
这段代码中返回的byte
切片底层指向的是整个文件的数据流([]byte
),因为它引用了原始数组,所以GC
不能及时回收底层数组的空间,就这么一小段代码,就可能导致系统会长时间保存整个文件数据,虽然这不是传统意义上的内存泄露,但是它或多或少会拖慢系统的整体性能;我们可以尝试将需要的数据复制到一个新的切片中(go语言的数据传值是有一定的代价的,但是在这里可以阻断对原始数组的依赖):
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = regexp.MustCompile("[0-9]+").Find(b)
return append([]byte{}, b...)
}
类似的问题,在删除切片元素时可能会遇到。假设切片里存放的是指针对象,那么下面删除末尾的元素后,被删除的元素依然被切片底层数组引用,从而导致不能及时被自动垃圾回收器回收(这要依赖回收器的实现方式):
var a []*int{ ... }
a = a[:len(a)-1] // 被删除的最后一个元素依然被引用, 可能导致GC操作被阻碍
保险的方式是先将需要自动内存回收的元素设置为nil
,保证自动回收器可以发现需要回收的对象,然后再进行切片的删除操作:
var a []*int{ ... }
a[len(a)-1] = nil // GC回收最后一个元素内存
a = a[:len(a)-1] // 从切片删除最后一个元素
当然,如果切片存在的周期很短的话,可以不用刻意处理这个问题。因为如果切片本身已经可以被GC
回收的话,切片对应的每个元素自然也就是可以被回收的了。
slice的强制类型转换
为了安全,当两个切片类型[]T
和[]Y
的底层原始切片类型不同时,Go语言是无法直接转换类型的。不过安全都是有一定代价的,有时候这种转换是有它的价值的——可以简化编码或者是提升代码的性能。比如在64位系统上,需要对一个[]float64
切片进行高速排序,我们可以将它强制转为[]int
整数切片,然后以整数的方式进行排序(因为float64
遵循IEEE754
浮点数标准特性,当浮点数有序时对应的整数也必然是有序的,感兴趣的朋友可以试试这个sort.Ints
的排序性能是优于sort.Float64s
这个的):
import "sort"
var a = []float64{4, 2, 5, 7, 2, 1, 88, 1}
func SortFloat64Fast(a []float64) {
// 通过 reflect.SliceHeader 更新切片头部信息实现转换
var c []int
aHdr := (*reflect.SliceHeader)(unsafe.Pointer(&a))
cHdr := (*reflect.SliceHeader)(unsafe.Pointer(&c))
*cHdr = *aHdr
// 以int方式给float64排序
sort.Ints(c)
}
分别取到两个不同类型的切片头信息指针,任何类型的切片头部信息底层都是对应reflect.SliceHeader
结构,然后通过更新结构体方式来更新切片信息,从而实现a
对应的[]float64
切片到c
对应的[]int
类型切片的转换.
前提是要保证[]float64
中没有NaN
和Inf
等非规范的浮点数(因为浮点数中NaN
不可排序,正0和负0相等,但是整数中没有这类情形)
那么问题来了,这个SliceHeader
又是个什么东西?
这个SliceHeader
其实是slice
运行时的具体表现,他的结构如下:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
正好对应Slice的三要素,Data
指向具体的底层数据源数组,Len
代表长度,Cap
代表容量。
既然Slice就是SliceHeader
,那么我们就可以把Slice转化为SliceHeader