go语言的Slice-补充说明

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中没有NaNInf等非规范的浮点数(因为浮点数中NaN不可排序,正0和负0相等,但是整数中没有这类情形)

那么问题来了,这个SliceHeader又是个什么东西?

这个SliceHeader其实是slice运行时的具体表现,他的结构如下:

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

正好对应Slice的三要素,Data指向具体的底层数据源数组,Len代表长度,Cap代表容量。

既然Slice就是SliceHeader,那么我们就可以把Slice转化为SliceHeader

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值