【问题标题】:Why Golang's slice operation so complicated为什么Golang的切片操作这么复杂
【发布时间】:2016-11-22 22:28:06
【问题描述】:

这是我在 Golang 的第一天,当我尝试它的切片操作时,比如append(),有一件事情让我很困惑:

package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    a:= s[2:4];
    a = append(a, 1000, 1001);
    a[1] = 100; 
    printSlice("a:", a)
    printSlice("s:", s)
}

func printSlice(title string, s []int) {
    fmt.Printf("%s  len=%d cap=%d %v\n", title,  len(s), cap(s), s)
}

当我只将两个数字附加到 a 时,例如:

a = append(a, 1000, 1001);

...结果是:

a:  len=4 cap=4 [5 100 1000 1001]
s:  len=6 cap=6 [2 3 5 100 1000 1001]

我认为,a 显示为对 s 的引用。

但是当我将那行代码更改为:

a = append(a, 1000, 1001, 1002);

...结果变成:

a:  len=5 cap=8 [5 100 1000 1001 1002]
s:  len=6 cap=6 [2 3 5 7 11 13]

我认为,a 已重新分配给另一段内存,以保存整个内容,并分离对 s 的引用。

这太不一致了,让我很困惑,有时很容易犯这个错误(比如当你有一个随机数量的值要附加时)。

为什么 Golang 是这样设计的?如果我只想要 JavaScript 的 slicepush 中的操作,如何避免这种情况?

【问题讨论】:

标签: go


【解决方案1】:

这是关于如何在 Go 中实现切片的问题。

slice's struct 看起来像:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

因此,切片具有长度和容量。如果您尝试将项目附加到切片以使其超出当前容量,则会在下面创建一个新数组来保存新数据,但由于之前的子切片可能仍指向旧数组,因此它会保持原样,直到有没有更多的参考。


现在假设我们有一个切片 A: [1, 2, 3, 4, 5, 6] 和一个子切片 B 指向 A: [4, 5, 6] 中的最后 3 个项目。

[1, 2, 3, 4, 5, 6]
 ^        ^
 |        |
 |        B------
 |
 A---------------  

现在,如果我们将一个项目附加到B,那么根据您的预期行为,它也应该更新A,因此将创建一个新数组。如果子切片的大小与实际数组相比较小(用于从原始数组复制 1000 项追加 1 项),这可能会效率低下。

另外,为了保持一致,所有其他指向旧数组的引用(子切片)都必须更新为指向新数组中的适当位置,这意味着我们必须在切片中存储其他信息,例如 start指数。如果我们有一个子切片的一个子切片,这可能会变得很棘手。

因此当前的实现是有意义的。


这里推荐的方法是制作 subslice 的副本而不是直接对其进行处理以防止此类问题。拥有副本的另一个优点是,如果原始切片很大并且不再有引用,那么它可以被垃圾收集,但如果它有一个子切片,那么原始数组将保留在内存中,直到子切片仍然引用给它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-11
    • 2019-02-15
    • 1970-01-01
    • 1970-01-01
    • 2014-11-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-18
    相关资源
    最近更新 更多