Go Slice的扩容机制

Go Slice的扩容机制

​ 在Go语言中使用append()函数向Slice添加元素,扩容也是发生在append的调用中,当切片内部的容量,不足以容纳新增元素时就会触发Slice的扩容。

并非每次调用append()函数都会触发扩容,因为扩容涉及到内存分配,会减缓append的速度。本文将从底层分析,以展现Slice切片扩容的具体策略。

扩容机制

1.扩容时机

func SliceExpansion() {
	sliceA := make([]int, 3, 4)
	sliceA = append(sliceA, 1) // 未扩容
	fmt.Printf("SliceA len: %v, cap: %v\n", len(sliceA), cap(sliceA))
	sliceB := make([]int, 3, 3)
	sliceB = append(sliceB, 1) // 触发扩容
	fmt.Printf("SliceB len: %v, cap: %v\n", len(sliceB), cap(sliceB))
}

Go Slice的扩容机制_第1张图片

​ 可见SliceA 初始len=3,初始cap=4;SliceB 初始len=3,初始cap=3,向SliceA 追加一个元素时,由于SliceA追加元素后len=cap发生扩容,且扩容后容量为6,看上去好像扩容就是在原来Slice长度的基础上增加一倍容量,事实上也是如此吗?

2.扩容策略

​ 当Slice的当前容量不足以容纳新增元素与原长度之和时会发生Slice的扩容,扩容的核心逻辑代码为 growSlice()

func growslice(et *_type, old slice, cap int) slice {

	// 将当前容量取出
	newcap := old.cap
	// 当前容量*2
	doublecap := newcap + newcap
	// cap:预估容量是否大于两倍的当前容量
	if cap > doublecap {
		// 新容量 = 预估容量
		newcap = cap
	} else {
		// 当前容量是否小于1024
		if old.cap < 1024 {
			// 两倍扩容
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			// 新容量 = 循环增加1.25倍,直至newcap > cap
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
            // 如果经过循环1.25增加,最终newcap计算值溢出,即超过了int的最大范围,则最终容量就是新申请的容量
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
	............
    // 内存分配相关
    ............
	// 将原数组复制到新地址
	memmove(p, old.array, lenmem)
	// 返回新创建的结构体
	return slice{p, old.len, newcap}
}

上面代价显示了Slice扩容的核心逻辑,Go语言中切片的扩容策略为:

  • **策略一:**如果新申请的容量(cap)大于2倍的旧容量(old.cap),则最终容量(nwecap)是新申请的容量。
  • **策略二:**如果不满足策略一
    • 如果旧切片len<1024,则最终容量是就容量的2倍,即newcap = doublecap。
    • 如果旧切片len>=1024,则最终容量循环增加1.25倍,直至newcap > cap 为止。
  • **策略三:**在进行循环1.25倍计算时,最终容量计算值发生溢出,即超过了int的最大范围,则最终容量就是新申请的容量。

你可能感兴趣的:(Golang,golang,java,大数据)