Golang中切片容量为什么比预期的大

Golang中切片容量为什么比预期的大 对于下面提到的代码:

package main
import ( "fmt")
func main() {
a := []int{12} // … 让编译器确定长度
fmt.Println(len(a))
fmt.Println(cap(a))

a=append(a,112)

fmt.Println(a)	
fmt.Println(len(a))
fmt.Println(cap(a))

a=append(a,234)

fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))}

输出: 1 1 [12 112] 2 2 [12 112 234] 3 4

请问谁能解释一下为什么输出的最后一行是4而不是3?


更多关于Golang中切片容量为什么比预期的大的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

更多关于Golang中切片容量为什么比预期的大的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这种神奇的效果来自于 append 函数。当 append 函数发现当前切片的容量不足以容纳更多元素时,它会创建一个容量翻倍的新切片。

package main

import "fmt"

func main() {
    var s []int
    printSlice(s)

// append works on nil slices.
    s = append(s, 0)
    printSlice(s)

// The slice grows as needed.
    s = append(s, 1)
    printSlice(s)

// We can add more than one element at a time.
    s = append(s, 2)
    printSlice(s)

// We can add more than one element at a time.
    s = append(s, 3)
    printSlice(s)

// We can add more than one element at a time.
    s = append(s, 5, 6, 7, 8, 9)
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

输出:

len=0 cap=0 [] len=1 cap=1 [0] len=2 cap=2 [0 1] len=3 cap=4 [0 1 2] len=4 cap=4 [0 1 2 3] len=9 cap=10 [0 1 2 3 5 6 7 8 9]

如果容量是成倍增长的,为什么最后容量是10,而不是16?有什么想法吗?

这是因为Go语言切片在容量不足时进行扩容的机制导致的。当切片容量不足以容纳新元素时,append()函数会创建一个新的底层数组,容量会按照特定算法增长,而不是简单地增加1。

在Go 1.18+版本中,切片的扩容规则大致如下:

  • 当原切片容量小于256时,新容量会翻倍(2倍)
  • 当原切片容量大于等于256时,新容量会按照公式 newcap = oldcap + (oldcap + 3*256)/4 增长

在你的代码中:

package main

import "fmt"

func main() {
    a := []int{12}
    fmt.Printf("初始状态: len=%d, cap=%d\n", len(a), cap(a)) // len=1, cap=1
    
    a = append(a, 112)
    fmt.Printf("第一次append后: len=%d, cap=%d\n", len(a), cap(a)) // len=2, cap=2
    
    a = append(a, 234)
    fmt.Printf("第二次append后: len=%d, cap=%d\n", len(a), cap(a)) // len=3, cap=4
}

具体过程:

  1. 初始切片 a 长度为1,容量为1
  2. 第一次 append(a, 112) 时,容量1不够,需要扩容。由于原容量1<256,新容量翻倍为2
  3. 第二次 append(a, 234) 时,容量2不够,再次扩容。原容量2<256,新容量翻倍为4

所以最终长度为3,容量为4。这是为了减少频繁的内存分配,提高性能。你可以通过查看 runtime 包的源码来了解具体的扩容逻辑:

// runtime/slice.go 中的扩容函数(简化示意)
func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {
    // ...
    newcap := oldCap
    doublecap := newcap + newcap
    if newLen > doublecap {
        newcap = newLen
    } else {
        const threshold = 256
        if oldCap < threshold {
            newcap = doublecap
        } else {
            // 大于等于256时的增长策略
            for 0 < newcap && newcap < newLen {
                newcap += (newcap + 3*threshold) / 4
            }
        }
    }
    // ...
}

这种设计在需要多次追加元素时能显著提升性能,因为减少了内存分配的次数。

回到顶部