Go 切片

Go 切片

切片结构

源码包 src/runtime/slice.go 中 定义 slice 的结构为

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

array 指针指向底层数组
len 表示切片长度
cap 表示切片容量

切片扩容机制

go1.18之后:
growslice 方法中可以得知go切片扩容机制为:

1.当新切片需要的容量cap大于两倍扩容的容量,则直接按照新切片需要的容量扩容;

2.当原 slice 容量 < threshold 的时候,新 slice 容量变成原来的 2 倍;

3.当原 slice 容量 > threshold,进入一个循环,每次容量增加(旧容量+3*threshold)/4
// 截取growslice方法中部分代码
if newLen > doublecap {
    newcap = newLen
} else {
    const threshold = 256
    if oldCap < threshold {
        newcap = doublecap
    } else {
        // Check 0 < newcap to detect overflow
        // and prevent an infinite loop.
        for 0 < newcap && newcap < newLen {
            // Transition from growing 2x for small slices
            // to growing 1.25x for large slices. This formula
            // gives a smooth-ish transition between the two.
            newcap += (newcap + 3*threshold) / 4
        }
        // Set newcap to the requested cap when
        // the newcap calculation overflowed.
        if newcap <= 0 {
            newcap = newLen
        }
    }
}

切片共享底层数组的特性

package main

import "fmt"

func main() {
	var arr = make([]int, 3, 5) // [0 0 0] _ _

	// arr brr 共享底层数组
	brr := append(arr, 8) // [0 0 0 8] _
	
	brr[0] = 1

	fmt.Println(arr) //[1 0 0]
	fmt.Println(brr) //[1 0 0 8]
}

母子切片内存共享与内存分离

package main

import "fmt"

func main() {
	var arr = make([]int, 3, 5) // [0 0 0] _ _ // len=3 cap=5

	s1 := arr[1:3] // 0 [0 0] _ _ // len=2 cap=4

	// 母子切片内存共享
	arr[1] = 1
	fmt.Println(arr) // [0 1 0] _ _
	fmt.Println(s1)  // 0 [1 0] _ _

	fmt.Println("------AAAAAAAAAAAAAAAAAAAAAAAAAA------")

	// 母子切片 append 会把新元素放到 母切片预留空间中
	s1 = append(s1, 8)

	fmt.Println(arr) // [0 1 0] 8 _
	fmt.Println(s1)  // 0 [1 0 8] _
	fmt.Println("------BBBBBBBBBBBBBBBBBBBBBBBBBB------")

	// 此时仍然共享内存
	arr[1] = 7
	fmt.Println(arr) // [0 7 0] 8 _
	fmt.Println(s1)  // 0 [7 0 8] _

	fmt.Println("------CCCCCCCCCCCCCCCCCCCCCCCCCC------")

	// 当子切片(或母切片)不断执行append 耗完母切片预留空间 就会发生内存分离
	s1 = append(s1, 9)
	s1 = append(s1, 10)
	fmt.Println(arr) // [0 7 0] 8 9
	fmt.Println(s1)  // [7 0 8 9 10]

	fmt.Println("------DDDDDDDDDDDDDDDDDDDDDDDDDD------")
	
	// 内存分离后
	arr[1] = 4
	fmt.Println(len(arr), cap(arr)) // len=3 cap=5
	fmt.Println(len(s1), cap(s1)) // len=5 cap=8
	fmt.Println(arr) // [0 4 0] 8 9
	fmt.Println(s1)  // [7 0 8 9 10]
}

package main

import "fmt"

func main() {

	arr := []int{1, 2, 3, 4, 5, 6}

	s1 := arr[:3]
	s2 := arr[3:]
	fmt.Println(s1, len(s1), cap(s1)) // s1的 len=3 cap=6
	fmt.Println(s2, len(s2), cap(s2)) // s2的 len=3 cap=3

	// 模拟对s1进行处理
	foo(s1)
	fmt.Println(s1)  // [1 2 3]
	fmt.Println(s2)  // [11 5 6]
	fmt.Println(arr) // [1 2 3 11 5 6]

	// 以上代码中出现了2处与预期不符合的情况
	// 1. 在foo()中既然能将sli切片(也就是s1)添加的第四位值(由10改为11)更改,
	// 为何最后main()中的s1没有发生改变?
	// 2. 对s1进行处理 却影响了s2的值?

	// 第一点,在函数 foo() 中对 sli 进行了 append 操作,由于go值传递的原因,
	// sli 是 s1 切片结构(切片结构:slice结构体,由指向底层数组的指针,切片的长度,切片的容量构成)的副本
	// 因此,对 sli 的扩容不会影响到 s1 的切片结构,即 s1 的len、cap不会发生变化,所以切片s1不会发生变化。
	// 第二点,值得注意的是 s1 的len=3,而cap=6,这意味着对s1进行 append 一个元素(foo中是10)不会发生扩容,
	// 也就是 arr 和 s1 不会发生内存分离,他们仍然共用底层数组,sli指向数组的指针也没变,
	// 所以当sli更改数组数据时,共用底层数组的切片都发生了改变,也就同时影响了 arr 和 s2。 
}
func foo(sli []int) {
	sli = append(sli, 10)
	sli[3] = 11
}

返回切片会导致内存泄漏

func foo() []int {
	
	// 假设 arr 是一个大容量切片
	var arr = make([]int, 30000, 30000)
	
	s1 := arr[1:3]

	// s1 仍然持有底层数组
	// 只要 s1 没有被gc回收, arr就一直得不到释放
	return s1
}

在函数参数中使用切片指针

package main

import "fmt"

func main() {
	var arr = make([]int, 3, 5) // [0 0 0] _ _

	f1(&arr)
	fmt.Println(arr) // [0 0 0 9] _

	f2(&arr)
	fmt.Println(arr) // [0 0 9] _ // len=3 cap=4

	f3(arr)
	fmt.Println(arr) // [1 0 9]
}

func f1(arr *[]int) {
	// len 和 cap 要改变时,需要传切片指针 例如append 操作
	*arr = append(*arr, 9)
}
func f2(arr *[]int) {
	// 指向数组的指针变了
	*arr = (*arr)[1:]
}
func f3(arr []int) {
	// slice结构体里的3个 field 都不变 只改变底层数组,不需要传切片指针
	arr[0] = 1
}
posted @   等你下课啊  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示