分析append引出的切片内存问题


分析append引出的切片内存问题


今天群里讨论一个关于切片append的坑

a := []int{1}
a = append(a,2)
a = append(a,3)

b := append(a,4)
c := append(a,5)
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)

在这里插入图片描述
一般思路我们是想到添加元素后b为[1,2,3,4],c为[1,2,3,5],但是为什么结果会是两个[1,2,3,5],4去哪儿了?其实一般思路是错误的。在理解append函数用法之前,我们应该先了解golang的切片内存模式是什么样的。
将源代码改为输出切片的长度和容量,还有切片引用的地址

a := []int{1}
fmt.Println("cap(a) =", cap(a), "len(a)=", len(a), "ptr(a) =", &a[0])

a = append(a, 2)
fmt.Println("cap(a) =", cap(a), "len(a)=", len(a), "ptr(a) =", &a[0])

a = append(a, 3)
fmt.Println("cap(a) =", cap(a), "len(a)=", len(a), "ptr(a) =", &a[0])

b := append(a, 4)
fmt.Println("cap(a) =", cap(a), "len(a)=", len(a), "len(b)=", len(b), "ptr(a) =", &a[0], "ptr(b) =", &b[0])

c := append(a, 5)
fmt.Println("cap(a) =", cap(a), "len(a)=", len(a), "len(c)=", len(c), "ptr(a) =", &a[0], "ptr(c) =", &c[0])

在这里插入图片描述


先来理解以下代码深入理解切片内存,运行观察结果

//创建底层数组,在底层数组基础上创建切片
	fmt.Println("------1.创建底层数组,在底层数组基础上创建切片------")
	num := [10]int{0,1,2,3,4,5,6,7,8,9}//len=10
	s1 := num[:5]
	s2 := num[3:8]
	s3 := num[5:]
	s4 := num[:]
	fmt.Println("num:", num)//[0,1,2,3,4,5,6,7,8,9]
	fmt.Println("s1:", s1)//[0,1,2,3,4]
	fmt.Println("s2:", s2)//[3,4,5,6,7]
	fmt.Println("s3:", s3)//[5,6,7,8,9]
	fmt.Println("s4:", s4)//[0,1,2,3,4,5,6,7,8,9]
	fmt.Printf("%p\n",&num) //0xc000014230
	fmt.Printf("%p\n",s1)  //0xc000014230,与num的地址相同
	fmt.Printf("s1  len:%d ,cap:%d \n",len(s1),cap(s1)) // len:5 ,cap:10
	fmt.Printf("s2  len:%d ,cap:%d \n",len(s2),cap(s2)) // len:5 ,cap:7
	fmt.Printf("s3  len:%d ,cap:%d \n",len(s3),cap(s3)) // len:5 ,cap:5
	fmt.Printf("s4  len:%d ,cap:%d \n",len(s4),cap(s4)) //len:10 ,cap:10

    //修改底层数组
    fmt.Println("------2.修改底层数组------")
	num[4] = 100
	fmt.Println(s1) //[0 1 2 3 100]
	fmt.Println(s2) //[3 100 5 6 7]
	fmt.Println(s3) //[5 6 7 8 9]
	fmt.Println(s4) //[0 1 2 3 100 5 6 7 8 9]

	//修改切片
	fmt.Println("------3.修改切片------")
	s2[1] = 99
	fmt.Println(num) //[0 1 2 3 99 5 6 7 8 9]
	fmt.Println(s1) //[0 1 2 3 99]
	fmt.Println(s2) //[3 99 5 6 7]
	fmt.Println(s3) //[5 6 7 8 9]
	fmt.Println(s4) //[0 1 2 3 99 5 6 7 8 9]

	fmt.Println("------4.使用append修改切片内容------")
	s1 = append(s1,1,1,1,1)
	fmt.Println(num) //[0 1 2 3 99 1 1 1 1 9]
	fmt.Println(s1) //[0 1 2 3 99 1 1 1 1]
	fmt.Println(s2) //[3 99 1 1 1]
	fmt.Println(s3) //[1 1 1 1 9]
	fmt.Println(s4) //[0 1 2 3 99 1 1 1 1 9]
	fmt.Println(len(s1),cap(s1)) //len=9,cap=10

	fmt.Println("-------5.append添加元素扩容-------")
	s1 = append(s1,2,2,2,2,2) //因为s1的len=9,cap=10,添加5个元素cap不够,只能扩容重新指向一个新的底层数组
	fmt.Println(num) //[0 1 2 3 99 1 1 1 1 9]
	fmt.Println(s1) //[0 1 2 3 99 1 1 1 1 2 2 2 2 2]
	fmt.Println(s2) //[3 99 1 1 1]
	fmt.Println(s3) //[1 1 1 1 9]
	fmt.Println(s4) //[0 1 2 3 99 1 1 1 1 9]
	fmt.Printf("%p\n",&num) //0xc000014230,地址没变
	fmt.Printf("%p\n",s1) //0xc000086000,地址改变,这是因为扩容后重新指向了一个新的底层数组
	fmt.Println(len(s1),cap(s1)) //s1的len=14,,cap=20.cap扩容是在原来10的基础上成倍扩容

观察以上代码,不难发现,每一个切片s都引用了同一个底层数组num,即切片本身不存储数据,都是底层数组存储数据,而切片只不过是对底层数组的一段引用,直接修改切片内容的同时,底层数组也会随其改变。往切片添加数据时,若没有超过切片的cap,那么直接添加,并且添加的值会相应覆盖底层数组的原值(如上述代码4步骤中往s1后添加多个数据)


切片是如何内存分配以及扩容的?

s := []int{1,2,3} //len:3,cap:3
s = append(s,4,5) //len:5,cap:6
s = append(s,6,7,8) //len:8,cap:12
s = append(s,9,10) //len:10,cap:12

在这里插入图片描述

再修改代码运行结果

a := []int{1}
	a = append(a,2)
	a = append(a,3)

	b :=append(a,4)
    fmt.Println(b)
	fmt.Println("----分割线-----")
	c :=append(a,5)
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)

在这里插入图片描述
很明显问题出在b和c定义赋新切片的操作
关于切片的append方法,官方给出的解释如下

如何使用append函数?

slice2 := append(slice1,23,15)

以上对切片slice1进行append操作,该操作遵循以下原则:
1.append函数对一个切片slice1进行追加操作,并返回另一个长度为len(slice1)+追加个数的切片,原切片不被改动,两个切片所指向的底层数组可能是同一个也可能不是,取决于第二条
2.slice1是对底层数组的一段引用,若append追加完之后没有突破slice1的容量,则实际上追加的数据改变了其底层数组对应的值,并且append函数返回对底层数组的新引用(切片);若append追加的数量突破了slice1的最大容量(底层数组长度固定,无法增加长度赋予新值),则Go会在内存中申请新的数组(数组内的值为追加操作之后的值),并返回对新数组的引用(切片)

(人话来说就是先判断切片容量,没满则修改,满就扩容成新的返回新的切片)

在这里插入图片描述

切片共享内部结构

在这里插入图片描述
在这里插入图片描述

a := []int{1,2}
//fmt.Println(cap(a))//2
b := append(a[0:1],3)
fmt.Println(a[1:2])//[3]
c := append(a[1:2],4)
//fmt.Println(cap(c))//2
fmt.Println(a)//[1,3]
fmt.Printf("a的地址是: %p \n",&a[0])//0xc0000a0090 
fmt.Println(b)//[1,3]//切片容量没满,可修改内容
fmt.Printf("b的地址是: %p \n",&b[0])//0xc0000a0090 
fmt.Println(c)//[3,4]//切片容量已满,新切片
fmt.Printf("c的地址是: %p \n",&c[0])//0xc0000a00c0 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值