导航菜单

切片扩容机制

🟡 中等

题目描述

解释 Go 语言中切片的扩容机制。以下代码的输出是什么?

func main() {
    s := make([]int, 0, 3)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
    
    s = append(s, 1)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
    
    s = append(s, 2)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
    
    s = append(s, 3)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
    
    s = append(s, 4)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

期望输出

len=0 cap=3 []
len=1 cap=3 [1]
len=2 cap=3 [2]
len=3 cap=3 [3]
len=4 cap=6 [1 2 3 4]

提示

  • 切片容量不足时会触发扩容
  • 扩容策略与 Go 版本有关
  • Go 1.18+ 前后有差异

解法

参考答案 (3 个标签)
切片 扩容机制 内存分配

核心概念

切片由三部分组成:

type slice struct {
    ptr unsafe.Pointer  // 指向底层数组的指针
    len int             // 长度
    cap int             // 容量
}

Go 1.18+ 扩容策略

// 简化版扩容逻辑
func growslice(oldCap, newLen int) int {
    newcap := oldCap
    doublecap := newcap + newcap
    
    if newLen > doublecap {
        newcap = newLen
    } else {
        const threshold = 256
        
        if oldCap < threshold {
            // 小切片:容量翻倍
            newcap = doublecap
        } else {
            // 大切片:增长 25%
            for 0 < newcap && newcap < newLen {
                newcap += (newcap + 3*threshold) / 4
            }
        }
    }
    
    // 内存对齐(考虑到元素大小)
    // ... 省略对齐逻辑
    
    return newcap
}

扩容规则总结

旧容量预期容量新容量(Go 1.18+)增长比例
01取决于类型-
122100%
234100%
346100%
458100%
100%
256257512100%
512513848~65.6%
102410251408~37.5%

关键点

  1. 小切片(< 256):容量翻倍
  2. 大切片(≥ 256):增长约 25%
  3. 内存对齐:实际容量可能会因为内存对齐而更大
  4. 新数组:扩容会创建新数组,复制原数据

代码验证

func main() {
    s := make([]int, 0, 3)
    
    // cap = 3,未扩容
    s = append(s, 1, 2, 3)
    fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=3 cap=3
    
    // cap = 3 → 6,触发扩容
    s = append(s, 4)
    fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=4 cap=6
    
    // cap = 6,未扩容
    s = append(s, 5, 6)
    fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=6 cap=6
    
    // cap = 6 → 12,触发扩容
    s = append(s, 7)
    fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=7 cap=12
}

扩展:切片共享底层数组的问题

func main() {
    s1 := make([]int, 3, 5) // [0, 0, 0], cap=5
    s1[0], s1[1], s1[2] = 1, 2, 3
    
    // s2 共享 s1 的底层数组
    s2 := s1[1:3] // [2, 3], cap=4
    fmt.Println(s1, s2) // [1 2 3] [2 3]
    
    // 修改 s2 会影响 s1
    s2[0] = 99
    fmt.Println(s1, s2) // [1 99 3] [99 3]
    
    // 追加 s2 可能覆盖 s1 的元素
    s2 = append(s2, 4, 5)
    fmt.Println(s1, s2) // [1 99 3] [99 3 4 5]
    
    // 再追加会触发扩容,创建新数组
    s2 = append(s2, 6)
    fmt.Println(s1, s2) // [1 99 3] [99 3 4 5 6]
    // 此时 s2 和 s1 不再共享底层数组
}

搜索