理解Go中的slice

最近闲来无事,深入研究了slice在Golang中的实现并简要阅读了其相关的底层实现代码后,对于实际工作中的一些slice相关代码的写法与Bug有了一种豁然开朗的感觉。故记录下来,与君分享。

数组 vs 切片

对于初学者来说,我们必须分清楚数组与切片的区别。

在Go中,数组与其他语言并无太大区别,都是一段指定长度的连续内存空间。例如如下代码,我们声明一个长度为4,类型为int的数组a

var a [4]int

此时,Go会调用mallocgc函数为我们申请一段连续的内存空间,如下图所示。

而对于slice,其实际上是一种结构体,基于数组的一种抽象,相当于其他语言中的动态数组。

切片的表示

runtime包中,切片的定义如下。

type slice struct {
	array unsafe.Pointer // 底层数组
	len   int // 切片长度
	cap   int // 切片容量
}

array属性指向一个底层数组,实际数据存储在其中,对于同一个底层数组,其可以被多个切片引用。

len属性限制了切片的长度,通过arraylen属性,我们就能划分出数组上的一段数据。

cap属性表示切片的容量,其以array所指向的地址为起始位置,数组末尾为结束位置。两者之间的跨度即为cap的大小。

举例说明,这里我们创建一个切片。

	s1 := make([]byte, 4, 6)
	s1[0] = 'a'
	s1[1] = 'b'
	s1[2] = 'c'
	s1[3] = 'd'

其在内存中表示如下所示。

此时,我们再创建一个切片s2 := s1[1:3]。其表示如下所示。

由于s2是基于s1传建的,因此其指向了同一底层数组,不同的是,s2的array指向的是b元素的起始内存地址。显而易见,当我们执行s1[1] = 'x'语句时,s2[0]的值也会被改变为x

空切片 vs nil切片

nil切片var s []byte,如下图所示。

空切片s := make([]byte, 0),如下图所示。

nil切片array属性为nil,而空切片array属性为空数组的地址。

切片的扩容

当我们执行append语句时,Go会对切片进行动态扩容。那么其大致策略是怎样的呢?

首先,要明确,扩容针对的是切片的容量而非底层数组的实际长度!

情况1,当执行s1 = append(s1, 'e', 'f', 'g')语句时,由于总的元素个数超过cap,需要进行扩容。对于扩容,Go会根据实际情况使用一个合适新容量来创建一个新的底层数组,然后将原数组的数据copy到新数组中,最后再返回一个新的切片(详情可查看runtime包中的growslice函数)。

情况2,当执行s2 = append(s2, 'e')语句时,由于len+1依然小于cap故其并未扩容,只是将底层数组上的元素d覆盖为了e

显然,s1[4]的值也会因此而被改变为e

切片代码编写建议

在实际工作中,针对切片操作,我们应该注意哪些呢?

  1. 及时释放大切片。例如下述代码

    	for i := 0; i < 3; i++ {
    		tmp := make([]byte, 100000000)
        headers = append(headers, small[:10])
    	}
    

    虽然我们只使用了tmp的前10个字节,但是其指向的底层数组依然是原来tmp指向的大数组,因此GC不会回收该大数组,从而导致内存占用过大。为了避免此问题,我们可以创建一个小切片,然后将前10个字节复制过去,如下代码所示。

    	for i := 0; i < 3; i++ {
    		tmp := make([]byte, 100000000)
    		small := make([]byte, 10)
    		copy(small, tmp[:10])
    		headers = append(headers, small)
    	}
    
  2. append操作时,需要特别小心,避免因为使用同一底层数组而导致Bug。必要时,可采用append([]int{}, s...)的形式来创建一个新的切片。

  3. 使用for range语句时,例如

    for i, name := range names {
      ...
    }
    

    其中name变量是值拷贝,每一次迭代都将对应i位置上的值拷贝到变量name上,因此你无法通过改变name的值来改变切片names对应位置上的值。当然,我们都知道在迭代中修改原切片是十分不好的编程习惯。

参考

posted @ 2020-02-15 17:32  erenming  阅读(171)  评论(0编辑  收藏  举报