Golang Slice 的扩容机制,有什么注意点?

发布于 1周前 作者 sinazl 最后一次编辑是 5天前 来自 问答

Golang Slice 的扩容机制,有什么注意点?

Go 中切片扩容的策略是这样的: 首先判断,如果新申请容量大于 2 倍的旧容量,最终容量就是新申请的容 量 否则判断,如果旧切片的长度小于 1024,则最终容量就是旧容量的两倍 否则判断,如果旧切片长度大于等于 1024,则最终容量从旧容量开始循环 增加原来的 1/4, 直到最终容量大于等于新申请的容量 如果最终容量计算值溢出,则最终容量就是新申请容量

1 回复

Golang Slice 的扩容机制及注意点

在 Go 语言中,Slice(切片)是一种非常灵活且强大的数据结构,它是对数组的抽象,提供了动态数组的功能。Slice 的扩容机制是自动的,但了解其背后的原理对于编写高效、可预测的 Go 程序至关重要。

扩容机制

Go 的 Slice 在其底层通过数组实现,并包含三个字段:指向数组的指针、切片的长度(len)和切片的容量(cap)。当向 Slice 添加元素导致其长度超过容量时,Slice 会自动扩容。扩容策略大致是:

  • 如果当前 Slice 的长度小于 1024 个元素,则每次扩容会将容量加倍。
  • 如果当前 Slice 的长度大于等于 1024 个元素,则扩容时会增加 25% 的容量,或者增加至所需容量的两倍(取两者中较大的值)。

这种策略旨在平衡内存使用与扩容成本。

注意点

  1. 性能影响: 自动扩容虽然方便,但频繁扩容可能导致性能问题,因为每次扩容都可能涉及内存分配和元素复制。尽量预估 Slice 的大小,使用 make 函数预分配足够的容量。

    s := make([]int, 0, 100) // 初始容量为 100
    
  2. 切片共享底层数组: 当对 Slice 进行切片(如 s[start:end])时,新切片和原切片共享同一个底层数组。修改新切片中的元素可能会影响到原切片。

    a := []int{1, 2, 3, 4, 5}
    b := a[1:3] // b 和 a 共享底层数组
    b[0] = 100  // a 也被修改为 [1, 100, 3, 4, 5]
    
  3. 扩容后的容量可能超出预期: 由于扩容策略的存在,扩容后的容量可能远大于你实际需要的容量。这可能导致内存浪费。

  4. append 函数的返回值append 函数返回新的 Slice 和可能的新长度(虽然长度通常等于原长度加上追加的元素数)。如果 append 导致扩容,返回的将是新 Slice 的引用。

    s := []int{1, 2, 3}
    s = append(s, 4) // s 可能指向新的底层数组
    
  5. 避免不必要的扩容: 在循环中向 Slice 追加元素时,如果知道最终大小,最好预先分配足够的容量。

    var s []int
    for i := 0; i < 1000; i++ {
        s = append(s, i) // 可能导致多次扩容
    }
    
    // 更优的做法
    s = make([]int, 0, 1000)
    for i := 0; i < 1000; i++ {
        s = append(s, i) // 无需扩容
    }
    

理解 Slice 的扩容机制及其注意点,可以帮助你编写出更高效、更可预测的 Go 程序。

回到顶部