Golang中slice的底层数组实现解析

Golang中slice的底层数组实现解析

tail := make([]byte, 1000, 1000)
block := make([]byte, 1000)
block = append(tail, block...)

在上述代码中,“tail”和“block”将指向不同的底层数组。

tail := make([]byte, 1000, 2000)
block := make([]byte, 1000)
block = append(tail, block...)

在上述代码中,最后一行会将“block”中的1000个字节追加到“tail”中,然后将“block”指向“tail”。因此,在这种情况下,“tail”和“block”将指向相同的底层数组。

如何通过编程方式判断两个切片的底层数组是否指向相同的内存位置?

if &tail[0] == &block[0] {
    // 相同位置
}

上述代码可以工作,但这是符合Go语言习惯的代码吗?有没有更好的方法?


更多关于Golang中slice的底层数组实现解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

为什么你要检查一个切片是否与另一个切片共享相同的底层数组?你会想要处理像这样的情况吗?

a := make([]byte, 1000)
b := a[16:24]

更多关于Golang中slice的底层数组实现解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


通常更好的做法是制定一个策略,明确函数是否“拥有”传递给它的切片,或者如果它需要保留切片,是否需要创建副本。例如,io.Reader 及其相关函数被记录为不保留切片。此外,使用三索引切片(foo[:len(foo):len(foo)])来限制容量,确保追加操作会触发新的内存分配。接收到这种切片的函数无法覆盖 len 之后的字节。

func main() {
    fmt.Println("hello world")
}

假设 tailblock 是字节切片。下面的代码将 block 中的字节追加到 tail,然后将结果切片复制到 block

block = append(tail, block...)

在这行代码之前,tail 可能拥有足够的容量来容纳 block 中的所有字节。在这种情况下,不会分配新的内存,并且在这行代码之后,tailblock 将指向同一个底层数组。

如果 tail 没有足够的容量来容纳 block 中的所有字节,将会分配新的内存,并且 block 将指向该内存。在这种情况下,tailblock 将指向不同的底层数组。

同一个函数可能会被再次调用,我们可能会向 tail 追加字节。我们必须将 tail 设为 nil,以确保不会覆盖上面的 block

block = append(tail, block...)
tail = nil

如果 tailblock 指向相同的位置,底层数组仍然被 block 引用,因此不会被垃圾回收。

如果 tailblock 指向不同的位置,将 tail 设为 nil 意味着底层数组将被垃圾回收。下次调用此函数并向 tail 追加字节时,我们将分配新的内存位置。我们应该能够对此进行优化。

block = append(tail, block...)
if &tail[0] == &block[0] {
   // 相同位置
   tail = nil
} else {
   // 不同位置
   tail = tail[:0]
}

上面的代码确保 tail 底层数组的内存不会被不必要地释放。上面的 if 检查必须是准确的,因此有了这个问题。

在代码片段中,字节切片 ab 共享同一个底层数组,但是 b 使用的是 a 数组的一个子集。这不是我正在寻找的情况,但是我也想知道我们是否可以通过编程方式判断 ab 是否共享同一个底层数组。这是为了其他一些用例。

在Go中判断两个切片是否共享底层数组,可以通过比较它们的底层数组指针来实现。你提到的方法 &tail[0] == &block[0] 是可行的,但需要确保切片非空,否则会引发panic。

更健壮的方式是使用reflect.SliceHeader来获取底层数组指针,但需要注意这涉及unsafe操作。以下是两种实现方式:

方法1:使用切片首元素地址比较(需确保非空)

func shareArray(s1, s2 []byte) bool {
    if len(s1) == 0 || len(s2) == 0 {
        return false
    }
    return &s1[0] == &s2[0]
}

方法2:使用reflect.SliceHeader(可处理空切片)

import (
    "reflect"
    "unsafe"
)

func shareArray(s1, s2 []byte) bool {
    h1 := (*reflect.SliceHeader)(unsafe.Pointer(&s1))
    h2 := (*reflect.SliceHeader)(unsafe.Pointer(&s2))
    return h1.Data == h2.Data
}

方法3:使用unsafe.Pointer直接比较

import "unsafe"

func shareArray(s1, s2 []byte) bool {
    return unsafe.Pointer(&s1[:1][0]) == unsafe.Pointer(&s2[:1][0])
}

在实际代码中,第一种方法最为常见,因为它简单直接。但如果你需要处理空切片的情况,第二种方法更安全。需要注意的是,这些方法都涉及底层实现细节,应谨慎使用。

示例验证:

func main() {
    // 示例1:不同底层数组
    tail := make([]byte, 1000, 1000)
    block := make([]byte, 1000)
    block = append(tail, block...)
    fmt.Println("示例1共享数组:", shareArray(tail, block)) // false
    
    // 示例2:相同底层数组
    tail2 := make([]byte, 1000, 2000)
    block2 := make([]byte, 1000)
    block2 = append(tail2, block2...)
    fmt.Println("示例2共享数组:", shareArray(tail2, block2)) // true
}

在Go社区中,&s1[0] == &s2[0] 这种写法被认为是可接受的,只要你能确保切片非空。如果代码中可能遇到空切片,建议添加长度检查或使用更安全的方法。

回到顶部