Golang 对分片 append 是否会共享数据

Golang 对分片 append 是否会共享数据

Golang 官方对 slice 的说明见文章:https://blog.golang.org/slices-intro 大部分特别是前半部分,写的非常详细,但是最后关于append相关部分,就让人感觉浅尝辄止了。

首先,先写结论:append 返回的slice对象,有可能会共享入参slice的底层(意味着对返回对象slice的赋值会影响入参slice)也可能不共享入参slice的底层。

先给几个例子

例1:

package main

import (
"fmt"
)


func main() {
        s := make([]int, 3)
        s[1] = 1000

        fmt.Println(len(s), cap(s), s)
}

打印出的结果就是3 3 [0 1000 0],这个毫无争议。

例2:

package main

import (
"fmt"
)


func main() {
        s1 := make([]int, 3)
        s2 := append(s1, 1000)//s1 追加 值1000,返回值是s2

        fmt.Println("s2", len(s2), cap(s2), s2)
        fmt.Println("s1", len(s1), cap(s1), s1)

        fmt.Println("After modify s2")
        s2[1] = 999//对s2赋值,看看是否对s1有影响
        fmt.Println("s2", len(s2), cap(s2), s2)
        fmt.Println("s1", len(s1), cap(s1), s1)
}

打印结果

s2 4 6 [0 0 0 1000]
s1 3 3 [0 0 0]
After modify s2
s2 4 6 [0 999 0 1000]
s1 3 3 [0 0 0]

似乎,我们对s2的赋值(s2[1] = 999)没有影响s1,所以,append返回的对象,就和是s1没任何关系了?NO,我们来看例3

例3:

package main

import (
"fmt"
)


func main() {
        s1 := make([]int, 3)

        s_slice := s[1:2]
        s2 := append(s_slice, 1000)
        fmt.Println("s2", len(s2), cap(s2), s2)
        fmt.Println("s1", len(s1), cap(s1), s1)

        fmt.Println("After modify s2")
        s2[1] = 999
        fmt.Println("s2", len(s2), cap(s2), s2)
        fmt.Println("s1", len(s1), cap(s1), s1)
}
s2 2 2 [0 1000]
s1 3 3 [0 0 1000]
After modify s2
s2 2 2 [0 999]
s1 3 3 [0 0 999]

从结果来看,你看s1被同步修改了。例3 和 例2的唯一区别在于append的入参,s_slice和s1,这里 我们就要反过头来看看文章前面提到的官方文档:https://blog.golang.org/slices-intro 分片的属性包含3个(数组指针、cap、len),而例3中,s_slice和s1的区别就在于 他两 cap len不同(相关知识可以搜索其他文章或者直接看上面给的链接)。

所以,可以猜到,append的操作肯定是判断了 cap 或者len了。 cap 表示 了 slice的存储最大能力,len表示当前元素个数。

len(s1), cap(s1),都是3,表示 s1 容量是3,元素个数是3,即表示 s1满了,你在s1上操作append,那么必然需要新创建数组指针,即例2中,s2->数组指针 和 s1->数组指针 完全是独立的。这也就是为什么 append 入参 是 s1 时,对返回的s2的操作,根本不会影响s1。

回到 例3,入参 s_slice 的 cap 是 2,len 是1(因为s_slice 衍生自s1,s1容量是3,len是3,s_slice取[1:2],cap还空了一个),所以此时 s_slice 是有容量的。
对于有容量的slice,append操作,是在当前 指针数组后面插入值即可。这也就意味着,s2使用了s1的指针数组,修改s1会同步修改s1。

append实现的伪代码可以理解为

append(src, other) {
	ret = src
	//超过容量,就申请新的内存,否则返回对象的byte还是src的byte
	if len(other) + src->len > src->cap {
		byte = new(...);
		copy(byte, src->byte)
		ret->byte = byte//replaced by new buffer
	} //else ret->byte == src->byte

	copy(src->byte + src->len, other)
}

append居然还有两副面孔。

你可能感兴趣的:(golang,golang)