强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Go 语言完全指南 / 08 - 数组与切片:底层原理、扩容机制、copy、append

08 - 数组与切片

8.1 数组(Array)

数组是固定长度、相同类型元素的序列。

package main

import "fmt"

func main() {
    // 声明并初始化
    var a [5]int                    // 零值初始化: [0 0 0 0 0]
    b := [5]int{1, 2, 3, 4, 5}    // 字面量初始化
    c := [...]int{10, 20, 30}      // 编译器自动计算长度
    d := [5]int{0: 10, 4: 50}     // 指定索引初始化

    fmt.Println(a, b, c, d)
    // [0 0 0 0 0] [1 2 3 4 5] [10 20 30] [10 0 0 0 50]

    // 数组操作
    fmt.Println("长度:", len(b))     // 5
    fmt.Println("元素:", b[0], b[4]) // 1, 50

    // 遍历
    for i, v := range b {
        fmt.Printf("[%d]=%d ", i, v)
    }
    fmt.Println()

    // 修改元素
    b[0] = 100
    fmt.Println(b) // [100 2 3 4 5]
}

数组是值类型

func main() {
    a := [3]int{1, 2, 3}
    b := a         // 复制整个数组
    b[0] = 100
    fmt.Println(a) // [1 2 3](不受影响)
    fmt.Println(b) // [100 2 3]

    // 数组比较
    c := [3]int{1, 2, 3}
    fmt.Println(a == c) // true
}

二维数组

func main() {
    // 3x3 矩阵
    var matrix [3][3]int
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            matrix[i][j] = i*3 + j + 1
        }
    }
    fmt.Println(matrix)
    // [[1 2 3] [4 5 6] [7 8 9]]
}

8.2 切片(Slice)

切片是对底层数组的一个动态视图,包含三个要素:指针、长度、容量。

// 切片的内部结构
type slice struct {
    array unsafe.Pointer // 指向底层数组
    len   int            // 长度
    cap   int            // 容量
}
func main() {
    // 创建切片的多种方式
    s1 := []int{1, 2, 3, 4, 5}        // 字面量
    s2 := make([]int, 5)               // 长度 5,容量 5
    s3 := make([]int, 3, 10)           // 长度 3,容量 10
    s4 := []int{}                       // 空切片(非 nil)

    fmt.Println(s1, s2, s3, s4)
    fmt.Printf("s3: len=%d, cap=%d\n", len(s3), cap(s3)) // len=3, cap=10
}

切片 vs 数组

特性数组切片
长度固定动态
类型值类型引用类型
比较可用 ==只能与 nil 比较
声明[5]int[]int
传参复制整个数组传递引用(共享底层数组)

8.3 切片操作

切片表达式

func main() {
    s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    // 基本切片 [low:high]
    fmt.Println(s[2:5])    // [2 3 4]
    fmt.Println(s[:5])     // [0 1 2 3 4]
    fmt.Println(s[5:])     // [5 6 7 8 9]
    fmt.Println(s[:])      // [0 1 2 3 4 5 6 7 8 9]

    // 三索引切片 [low:high:max],控制容量
    s2 := s[2:5:7]
    fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2))
    // s2: [2 3 4], len=3, cap=5

    // ⚠️ 注意:切片不创建新底层数组
    s3 := s[0:3]
    s3[0] = 999
    fmt.Println(s) // [999 1 2 3 4 5 6 7 8 9](原数组也被修改)
}

append 函数

func main() {
    // 基本 append
    s := []int{1, 2, 3}
    s = append(s, 4)
    fmt.Println(s) // [1 2 3 4]

    // 追加多个元素
    s = append(s, 5, 6, 7)
    fmt.Println(s) // [1 2 3 4 5 6 7]

    // 追加另一个切片
    s2 := []int{8, 9, 10}
    s = append(s, s2...) // 展开切片
    fmt.Println(s) // [1 2 3 4 5 6 7 8 9 10]

    // append 可能触发扩容(创建新底层数组)
    s3 := make([]int, 3, 4)
    fmt.Printf("s3: len=%d, cap=%d, ptr=%p\n", len(s3), cap(s3), s3)
    s3 = append(s3, 1)
    fmt.Printf("s3: len=%d, cap=%d, ptr=%p\n", len(s3), cap(s3), s3)
    // 容量足够,不扩容
    s3 = append(s3, 2)
    fmt.Printf("s3: len=%d, cap=%d, ptr=%p\n", len(s3), cap(s3), s3)
    // 容量不足,扩容,底层数组地址变化
}

copy 函数

func main() {
    src := []int{1, 2, 3, 4, 5}
    dst := make([]int, 3)

    // 复制(取较小长度)
    n := copy(dst, src)
    fmt.Printf("复制了 %d 个元素: %v\n", n, dst) // [1 2 3]

    // 复制到指定位置
    dst2 := make([]int, 5)
    copy(dst2[2:], src[:3])
    fmt.Println(dst2) // [0 0 1 2 3]

    // 同一切片内复制(可以重叠)
    s := []int{1, 2, 3, 4, 5}
    copy(s[1:], s[:3])
    fmt.Println(s) // [1 1 2 3 5]

    // 字符串转 []byte
    str := "Hello, 世界"
    bytes := []byte(str)
    fmt.Println(bytes)

    // 创建切片的独立副本
    original := []int{1, 2, 3}
    cloned := make([]int, len(original))
    copy(cloned, original)
    cloned[0] = 999
    fmt.Println("original:", original) // [1 2 3]
    fmt.Println("cloned:", cloned)     // [999 2 3]
}

8.4 切片扩容机制

// Go 1.21+ 的扩容策略:
// 1. 如果新容量 > 2倍旧容量,直接使用新容量
// 2. 如果旧容量 < 256,新容量 = 2倍旧容量
// 3. 如果旧容量 >= 256,新容量 = 旧容量 * 1.25 + 192(近似)

func main() {
    s := make([]int, 0)
    prevCap := cap(s)
    for i := 0; i < 1000; i++ {
        s = append(s, i)
        if cap(s) != prevCap {
            fmt.Printf("len=%4d, cap=%4d, growth=%.2f\n",
                len(s), cap(s), float64(cap(s))/float64(prevCap))
            prevCap = cap(s)
        }
    }
}

⚠️ 注意:如果能预估切片大小,使用 make([]T, 0, estimatedSize) 预分配,减少扩容次数。

陷阱:切片共享底层数组

func main() {
    // 陷阱:子切片 append 修改了原数组
    original := []int{1, 2, 3, 4, 5}
    sub := original[1:3]   // [2 3], len=2, cap=4
    sub = append(sub, 999) // 写入 original[3] 的位置!
    fmt.Println(original)  // [1 2 3 999 5]
    fmt.Println(sub)       // [2 3 999]

    // 解决方案:使用三索引限制容量
    original2 := []int{1, 2, 3, 4, 5}
    sub2 := original2[1:3:3] // len=2, cap=2
    sub2 = append(sub2, 999) // 触发扩容,不影响原数组
    fmt.Println(original2)   // [1 2 3 4 5]
    fmt.Println(sub2)        // [2 3 999]

    // 解决方案:创建独立副本
    original3 := []int{1, 2, 3, 4, 5}
    sub3 := make([]int, 2)
    copy(sub3, original3[1:3])
    sub3 = append(sub3, 999)
    fmt.Println(original3) // [1 2 3 4 5]
}

8.5 切片删除操作

func main() {
    s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    // 删除索引 i 处的元素(保持顺序)
    i := 3
    s = append(s[:i], s[i+1:]...)
    fmt.Println(s) // [0 1 2 4 5 6 7 8 9]

    // 删除索引 i 处的元素(不保持顺序,更快)
    s2 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    i = 3
    s2[i] = s2[len(s2)-1]
    s2 = s2[:len(s2)-1]
    fmt.Println(s2) // [0 1 2 9 4 5 6 7 8]

    // 删除子切片 [i:j]
    s3 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    i, j := 3, 6
    s3 = append(s3[:i], s3[j:]...)
    fmt.Println(s3) // [0 1 2 6 7 8 9]

    // 清空切片(保留容量)
    s4 := []int{1, 2, 3, 4, 5}
    s4 = s4[:0]
    fmt.Println(s4, len(s4), cap(s4)) // [] 0 5

    // 释放内存(GC 可以回收底层数组)
    s5 := make([]int, 1000)
    s5 = nil
    // 或者 s5 = make([]int, 0)
}

8.6 切片插入操作

func main() {
    // 在索引 i 处插入元素
    s := []int{1, 2, 3, 7, 8}
    i := 3
    val := 99

    // 方法一:使用 append
    s = append(s[:i], append([]int{val}, s[i:]...)...)
    fmt.Println(s) // [1 2 3 99 7 8]

    // 方法二:更高效(减少内存分配)
    s2 := []int{1, 2, 3, 7, 8}
    s2 = append(s2, 0)      // 扩展一个位置
    copy(s2[i+1:], s2[i:])  // 后移元素
    s2[i] = val             // 插入值
    fmt.Println(s2)          // [1 2 3 99 7 8]

    // 插入多个元素
    s3 := []int{1, 2, 7, 8}
    insert := []int{3, 4, 5, 6}
    s3 = append(s3[:2], append(insert, s3[2:]...)...)
    fmt.Println(s3) // [1 2 3 4 5 6 7 8]
}

8.7 性能优化

import "testing"

// ❌ 差:多次 append,多次扩容
func bad() []int {
    var s []int
    for i := 0; i < 10000; i++ {
        s = append(s, i)
    }
    return s
}

// ✅ 好:预分配容量
func good() []int {
    s := make([]int, 0, 10000)
    for i := 0; i < 10000; i++ {
        s = append(s, i)
    }
    return s
}

// ❌ 差:不必要的 slice 创建
func filterBad(s []int) []int {
    var result []int
    for _, v := range s {
        if v > 0 {
            result = append(result, v)
        }
    }
    return result
}

// ✅ 好:原地过滤
func filterGood(s []int) []int {
    n := 0
    for _, v := range s {
        if v > 0 {
            s[n] = v
            n++
        }
    }
    return s[:n]
}

8.8 综合示例

package main

import (
    "fmt"
    "sort"
)

func main() {
    // 去重
    nums := []int{1, 3, 2, 3, 1, 4, 2, 5}
    seen := make(map[int]bool)
    unique := nums[:0]
    for _, v := range nums {
        if !seen[v] {
            seen[v] = true
            unique = append(unique, v)
        }
    }
    fmt.Println("去重:", unique) // [1 3 2 4 5]

    // 合并两个有序切片
    a := []int{1, 3, 5, 7}
    b := []int{2, 4, 6, 8}
    merged := make([]int, 0, len(a)+len(b))
    i, j := 0, 0
    for i < len(a) && j < len(b) {
        if a[i] <= b[j] {
            merged = append(merged, a[i])
            i++
        } else {
            merged = append(merged, b[j])
            j++
        }
    }
    merged = append(merged, a[i:]...)
    merged = append(merged, b[j:]...)
    fmt.Println("合并:", merged)

    // 分块
    chunk := func(s []int, size int) [][]int {
        var chunks [][]int
        for size < len(s) {
            s, chunks = s[size:], append(chunks, s[:size:size])
        }
        return append(chunks, s)
    }
    fmt.Println("分块:", chunk([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 3))
    // [[1 2 3] [4 5 6] [7 8 9]]

    // 洗牌(Fisher-Yates)
    s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    sort.Slice(s, func(i, j int) bool {
        return false // 使用真正的随机数代替
    })
}

🏢 业务场景

  1. 批量数据处理:切片是存储数据集合的基础
  2. 缓冲区:预分配切片做 I/O 缓冲区
  3. 滑动窗口:切片表达式实现固定大小窗口
  4. 栈/队列appends[:len(s)-1] 模拟栈
  5. 分页:切片表达式实现数据分页

📖 扩展阅读