GO操作切片数组时不当,数据被覆盖

本文深入探讨了Go语言中数组和切片的操作,特别是插入元素时的内存管理和地址问题。通过示例代码,解释了切片在长度和容量上的含义,以及如何通过切片创建新的视图。当切片需要扩容时,其容量如何按比例增长,以及在扩容过程中如何保持原有数据并重新分配内存。此外,文章还展示了在不同切片间进行元素修改时,由于共享底层数组,可能导致的意外数据同步现象。
摘要由CSDN通过智能技术生成

场景

在一个数组对象的 index 索引处插入一个值

func (list *ArrayList) Insert(index int, newval interface{}) error {}

原有代码

func (list *ArrayList) Insert(index int, newval interface{}) error {
	if index < 0 || index >= list.TheSize {
		return errors.New("index out of range")
	}

	// 1. 用一个临时数组保存该索引后的所有值,但是这里用了切片,所以底层的数据是同一个内存空间的数据
	tmpList := list.dataStore[index:]
	// 2. 将 index 前的数据当做一个单独数组,直接添加新值,实际在同一个内存空间进行操作
	// 并且将 index 处的值更新为 val,tmpList中对应内存地址的值也发生了改变
	newList := append(list.dataStore[:index], newval)
	// 3. 将两个数组拼接起来,实际上数据在上一步中已经发生了错误
	newList = append(newList, tmpList...)
	list.dataStore = newList
	list.TheSize++

	return nil
}

所以,若是对GO数组或者切片中的数据进行下标赋值类的操作时,需要格外注意内存地址相关问题

切片相关

说起切片,我想先从切片的长度和容量说起,切片在初始化中,可以指定长度和容量,这个长度和容量到底代表的是什么呢?

slice := make(t Type, length IntegerType, cap IntegerType)

切片的底层实际上是对一个数组片段的描述,它包含了一个指向数组片段的指针,片段的长度,以及该片段的最大值
在这里插入图片描述
我们可以从以下一个例子初步进行理解

func main() {
	a := []int{0, 1, 2}
	printSlice("a", a)

	b := a[:1]
	printSlice("b", b)

	c := b[:2]
	printSlice("c", c)

	d := c[1:]
	printSlice("d", d)
}

func printSlice(name string, a []int) {
	fmt.Println("slice", name, ", len:", len(a), "cap:", cap(a), "ptr:", &a[0])
}

运行结果

slice a , len: 3 cap: 3 ptr: 0xc000010360
slice b , len: 1 cap: 3 ptr: 0xc000010360
slice c , len: 2 cap: 3 ptr: 0xc000010360
slice d , len: 1 cap: 2 ptr: 0xc000010368

运行过程:

  1. 第一次输出,切片a,长度3,容量3
    在这里插入图片描述
  2. 切片b,在切片a的基础上,左指针指向 a[0],右指针指向 a[1],但是左闭右开,b 切片只包含元素 a[0],长度为1,底层的数组片段未发生变化,且开始位置未变化,所以容量为3
    在这里插入图片描述
  3. 切片c,在切片b的基础上,左指针指向 a[0],右指针指向 a[2],左闭右开,c切片包含两个元素,长度为2,同理底层的数组片段未发生变化,且开始位置未变化,容量为3
    在这里插入图片描述
  4. 切片d,在切片c的基础上,左指针指向 a[1],右指针指向 a[2],左闭右开,c切片包含1个元素,长度为1,同理底层的数组片段未发生变化,但是开始位置右移一位,容量为2
    在这里插入图片描述
    值得注意的是:从地址值看出,上述运行结果中该切片没有进行容量扩容,四个切片使用的是同一段内存地址,四个切片在使用下标进行数据修改时,会同时修改4个切片的数据
b[0] = 'b'
c[1] = 'c'

fmt.Println(a, b, c, d)

运行结果,可以对照上面的图片进行比较

[98 99 2] [98] [98 99] [99]

关于扩容
当切片的长度等于切片的容量,并需要继续增加元素时,切片的容量会进行扩容。
切片的容量一般来说会按照原切片容量的两倍进行扩容,当原切片长度小于1024时,新切片的容量会直接翻倍。而当原切片的容量大于等于1024时,会反复地增加25%,直到新容量超过所需要的容量。
当需要的容量超过原切片容量的两倍时,会使用需要的容量作为新容量。
同时切片会重新开辟一个切片内存地址,并存储原切片的数据
比如:

func main() {
	a := make([]int, 1) // 容量为1
	printSlice(a)
	a = append(a, 1, 2, 3) // 添加三个元素后,容量会直接变为4
	printSlice(a)
}

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

运行结果

len: 1 cap: 1 ptr: 0xc00000a0a0
len: 4 cap: 4 ptr: 0xc000010380

容量扩容相关例子:

func main() {
	a := make([]int, 5)
	b := a[:4]
	// a, b 是同一个内存地址上的数据,所以,修改 a[0] 的数据,切片 b 相同内存地址的值也会发生变化
	a[0] = 1
	fmt.Println("a:", a, "b:", b)
	// 运行结果:a: [1 0 0 0 0] b: [1 0 0 0]

	// 当 a append了一个数据之后,发生了扩容,切片 a 重新开辟了一个容量为 10 的内存空间,
	// 并将原切片数据复制了过去,此时再改变 a[0],切片 b 不再改变
	a = append(a, 0)
	a[0] = 2
	fmt.Println("a:", a, "b:", b)
	// 运行结果:a: [2 0 0 0 0 0] b: [1 0 0 0]
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值