场景
在一个数组对象的 index 索引处插入一个值
原有代码
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数组或者切片中的数据进行下标赋值类的操作时,需要格外注意内存地址相关问题
切片相关
说起切片,我想先从切片的长度和容量说起,切片在初始化中,可以指定长度和容量,这个长度和容量到底代表的是什么呢?
切片的底层实际上是对一个数组片段的描述,它包含了一个指向数组片段的指针,片段的长度,以及该片段的最大值
我们可以从以下一个例子初步进行理解
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
运行过程:
- 第一次输出,切片a,长度3,容量3
- 切片b,在切片a的基础上,左指针指向 a[0],右指针指向 a[1],但是左闭右开,b 切片只包含元素 a[0],长度为1,底层的数组片段未发生变化,且开始位置未变化,所以容量为3
- 切片c,在切片b的基础上,左指针指向 a[0],右指针指向 a[2],左闭右开,c切片包含两个元素,长度为2,同理底层的数组片段未发生变化,且开始位置未变化,容量为3
- 切片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]
}