为什么Go中不经常使用列表?

Vec*_*tor 65 arrays list go

我是Go的新手,对此非常兴奋.但是,在我广泛使用的所有语言中:Delphi,C#,C++,Python - 列表非常重要,因为它们可以动态调整大小,而不是数组.

在Golang中,确实存在一个list.List结构体,但我看到很少有关于它的文档 - 无论是Go Go Example还是我所拥有的三本Go书 - Summerfield,Chisnal和Balbaert--它们都花费大量时间在数组和切片上然后跳到地图.在源代码示例中,我也发现很少或没有使用list.List.

与Python不同,它似乎Range也不支持List - 大缺点IMO.我错过了什么吗?

切片当然很好,但它们仍然需要基于硬编码大小的数组.这就是List的用武之地.有没有办法在没有硬编码数组大小的Go中创建数组/切片?为什么List被忽略了?

Nic*_*ood 73

几乎总是当你想到一个列表时 - 在Go中使用切片.切片是动态调整大小的.它们下面是一块连续的内存,可以改变大小.

它们非常灵活,您将看到是否阅读了SliceTricks维基页面.

这是摘录: -

复制

b = make([]T, len(a))
copy(b, a) // or b = append([]T(nil), a...)
Run Code Online (Sandbox Code Playgroud)

a = append(a[:i], a[j:]...)
Run Code Online (Sandbox Code Playgroud)

删除

a = append(a[:i], a[i+1:]...) // or a = a[:i+copy(a[i:], a[i+1:])]
Run Code Online (Sandbox Code Playgroud)

删除而不保留​​订单

a[i], a = a[len(a)-1], a[:len(a)-1]
Run Code Online (Sandbox Code Playgroud)

流行的

x, a = a[len(a)-1], a[:len(a)-1]
Run Code Online (Sandbox Code Playgroud)

a = append(a, x)
Run Code Online (Sandbox Code Playgroud)

更新:这是一个博客文章的链接,所有关于来自go团队本身的切片,这很好地解释了切片和数组之间的关系以及切片内部.

  • 好的 - 这就是我一直在寻找的。我对切片有误解。您不必声明数组即可使用切片。您可以分配一个切片并分配后备存储。听起来类似于 Delphi 或 C++ 中的流。现在我明白了为什么所有关于切片的喧嚣。 (2认同)
  • @ComeAndGo,请注意,创建一个指向"静态"数组的切片的某些时间是一个有用的习惯用法. (2认同)
  • @FelikZ,切片在其后备阵列中创建"视图".通常,您事先知道函数将在其上运行的数据将具有固定大小(或者其大小不会超过已知的字节数;这对于网络协议来说非常常见).所以你可能只是声明一个数组来保存你的函数中的数据,然后将其切片 - 将这些切片传递给被调用的函数等. (2认同)

Vec*_*tor 44

几个月前,当我第一次开始调查Go时,我问过这个问题.从那时起,我每天都在阅读Go,并在Go中编码.

因为我没有得到这个问题的明确答案(虽然我已经接受了一个答案)我现在要根据我所学到的内容自己回答,因为我问过它:

有没有办法在没有硬编码数组大小的Go中创建数组/切片?

是.切片不需要硬编码数组slice来自:

var sl []int = make([]int,len,cap)
Run Code Online (Sandbox Code Playgroud)

此代码分配片sl,尺寸len,容量cap- lencap是可在运行时被分配的变量.

为什么被list.List忽略?

似乎主要原因list.List似乎在Go中很少受到关注:

  • 正如在@Nick Craig-Wood的回答中所解释的那样,对于无法通过切片完成的列表,通常更高效,并且使用更清晰,更优雅的语法,几乎无法做任何事情.例如范围构造:

    for i:=range sl {
      sl[i]=i
    }
    
    Run Code Online (Sandbox Code Playgroud)

    不能与列表一起使用 - 需要C样式for循环.在许多情况下,C++集合样式语法必须与列表一起使用: push_back等等.

  • 也许更重要的是,list.List它不是强类型的 - 它与Python的列表和词典非常相似,它允许在集合中混合各种类型.这似乎与Go的做法相反.Go是一种非常强类型的语言 - 例如,Go中从不允许隐式类型转换,即使upCast intint64必须是显式的.但是list.List的所有方法都采用空接口 - 任何事情都可以.

    我抛弃Python并转向Go的原因之一是因为Python的类型系统存在这种弱点,尽​​管Python声称"强类型"(IMO不是).Go list.List似乎是一种"杂种",出自C++ vector<T>和Python List(),并且在Go本身可能有点不合适.

如果在不久的将来某个时刻,我们会发现list.List在Go中被弃用,虽然它可能会保留,以适应那些罕见的情况,即使使用良好的设计实践,也可以最好地解决问题,这不会让我感到惊讶.拥有各种类型的集合.或许它可以为C家族开发人员提供一个"桥梁",让他们在了解切片的细微差别之前熟悉Go,这是Go,AFAIK独有的.(在某些方面,切片看起来类似于C++或Delphi中的流类,但并非完全相同.)

虽然来自Delphi/C++/Python背景,但在我初次接触Go时,我发现list.List比Go的片段更熟悉,因为我对Go更加熟悉,我已经回去并将所有列表更改为切片.我还没有发现任何东西slice和/或map不允许我这样做,所以我需要使用list.List.

  • 静态检查语言,在代码运行之前强制执行某些规则.像C这样的语言为您提供了一个原始类型系统:您的代码可能会正确地键入检查,但在运行时会爆炸.你继续这个频谱,你得到Go,这给你提供了比C更好的保证.然而,在OCaml这样的语言中也没有类型系统(这也不是频谱的终点).说"Go可能是那里最强类型的语言"是完全错误的.对于开发人员而言,了解不同语言的安全属性非常重要,这样他们才能做出明智的选择. (3认同)
  • Go缺少的具体例子:缺乏泛型会迫使你使用动态强制转换.缺少枚举/检查交换机完整性的能力进一步意味着动态检查其他语言可以提供静态保证. (3认同)

kos*_*tix 9

我认为这是因为没有太多关于它们的说法,因为一旦你吸收了使用泛型数据的主要Go成语,那么这个container/list包就是不言自明了.

在Delphi(没有泛型)或C中,您可以TObject在列表中存储指针或s,然后在从列表中获取时将它们转换回真实类型.在C++中,STL列表是模板,因此按类型进行参数化,在C#中(这些天)列表是通用的.

在Go中,container/list存储类型的值,interface{}这是一种特殊类型,能够表示任何其他(真实)类型的值 - 通过存储一对指针:一个指向所包含值的类型信息,以及指向该值的指针(或值直接,如果它的大小不大于指针的大小).因此,当您想要向列表中添加元素时,您只需将其作为类型为interface{}accept的值的函数参数.但是当你从列表中提取值,以及如何使用它们的真实类型时,你必须键入它们或者对它们进行类型切换 - 这两种方法只是做同样事情的不同方法.

这是一个从这里得到的例子:

package main

import ("fmt" ; "container/list")

func main() {
    var x list.List
    x.PushBack(1)
    x.PushBack(2)
    x.PushBack(3)

    for e := x.Front(); e != nil; e=e.Next() {
        fmt.Println(e.Value.(int))
    }
}
Run Code Online (Sandbox Code Playgroud)

这里我们获取元素的值e.Value(),然后将其断言为int原始插入值的类型.

您可以在"Effective Go"或任何其他入门书籍中阅读类型断言和类型开关.该container/list包的文档总结的所有方法列表的支持.

  • @ComeAndGo,正是Go的切片具有长度和容量所能做到的. (3认同)
  • @ComeAndGo,至于索引...从包的文档中可以清楚地看到`container/list`提供了一个双链表.这意味着索引是一个"O(N)"操作(你必须从头开始并遍历每个元素朝向尾部计数),而Go的基石设计范例之一没有*隐藏的性能成本;*与另一个一个是给程序员增加一些额外的负担(实现双链表的索引功能是一个10行的简单)是可以的.因此,容器只实现对其类型敏感的"规范"操作. (2认同)

Jam*_*dge 6

请注意,可以通过append()内置函数扩展 Go 切片。虽然这有时需要复制后备数组,但不会每次都发生,因为 Go 会使新数组过大,使其容量大于报告的长度。这意味着可以在没有另一个数据副本的情况下完成后续的追加操作。

虽然与使用链表实现的等效代码相比,您最终会得到更多的数据副本,但您无需单独分配列表中的元素,也无需更新Next指针。对于许多用途,基于数组的实现提供了更好或足够好的性能,因此这就是语言中所强调的。有趣的是,Python 的标准list类型也是数组支持的,并且在附加值时具有类似的性能特征。

也就是说,在某些情况下,链接列表是更好的选择(例如,当您需要从长列表的开头/中间插入或删除元素时),这就是提供标准库实现的原因。我猜他们没有添加任何特殊的语言功能来使用它们,因为这些情况比使用切片的情况少见。

  • 程序源代码中没有硬编码切片的大小,如果这就是您的意思的话。它可以通过 `append()` 操作动态扩展,正如我所解释的(有时会涉及数据复制)。 (3认同)

Man*_*ddy 5

来自: https: //groups.google.com/forum/#! msg/golang-nuts/mPKCoYNwsoU/tLefhE7tQjMJ

这在很大程度上取决于列表中元素的数量,
 真正的列表还是切片会更有效
 当您需要在列表的“中间”进行多次删除时。

#1
元素越多,切片的吸引力就越小。

#2
当元素的顺序不重要时,
 使用切片是最有效的
 通过将元素替换为切片中的最后一个元素来删除元素,并且
 重新切片切片以将 len 缩小 1
 (如 SliceTricks wiki 中所解释)

所以
使用切片
1. 如果列表中元素的顺序不重要,并且需要删除,只需
使用 List 将要删除的元素与最后一个元素交换,并重新切片为 (length-1)
2. 当元素较多时 (无论更多的意思)


There are ways to mitigate the deletion problem --
e.g. the swap trick you mentioned or
just marking the elements as logically deleted.
But it's impossible to mitigate the problem of slowness of walking linked lists.
Run Code Online (Sandbox Code Playgroud)

所以
使用切片
1。如果你需要遍历速度


Man*_*har 5

除非切片更新太频繁(删除,在随机位置添加元素),与链表相比,切片的内存连续性将提供出色的缓存命中率。

Scott Meyer 关于缓存重要性的演讲.. https://www.youtube.com/watch?v=WDIkqP4JbkE