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

时间:2014-01-24 06:41:56

标签: arrays list go

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

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

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

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

7 个答案:

答案 0 :(得分:63)

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

它们非常灵活,您会看到是否阅读了SliceTricks wiki page

以下是摘录: -

  

复制

b = make([]T, len(a))
copy(b, a) // or b = append([]T(nil), a...)
     

剪切

a = append(a[:i], a[j:]...)
     

删除

a = append(a[:i], a[i+1:]...) // or a = a[:i+copy(a[i:], a[i+1:])]
     

删除而不保留​​订单

a[i], a = a[len(a)-1], a[:len(a)-1]
     

流行音乐

x, a = a[len(a)-1], a[:len(a)-1]
     

a = append(a, x)

更新:这是来自go团队本身的blog post all about slices的链接,它可以很好地解释切片与数组和切片内部之间的关系。

答案 1 :(得分:41)

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

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

  

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

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

var sl []int = make([]int,len,cap)

此代码分配大小为sl且大小为len - caplen的大小为cap的切片list.List,这些变量可在运行时分配。

  

为什么忽略list.List

似乎主要原因for i:=range sl { sl[i]=i } 在Go中似乎很少受到关注:

  • 正如在@Nick Craig-Wood的回答中所解释的,有 几乎没有什么可以用无法完成的列表来完成 切片,通常更有效,更清洁,更多 优雅的语法。例如范围构造:

    push_back

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

  • 也许更重要的是,int不是强类型的 - 它与Python的列表和词典非常相似,它允许在集合中混合各种类型。这似乎与此相反 对Go的处理方式。 Go是一种非常强类型的语言 - 例如,Go中从不允许隐式类型转换,即使从int64list.List的upCast必须是 明确。但是list.List的所有方法都采用空接口 - 什么都可以。

    我放弃Python并转移到Go的原因之一是因为 虽然是Python,但是在Python的类型系统中存在这种弱点 声称自己是强力打字的#34; (国际海事组织,它不是)。 Go似乎vector<T> 是一种&#34; mongrel&#34;,诞生于C ++的List()和Python&#39; s list.List,在Go本身可能有点不合适。

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

虽然来自Delphi / C ++ / Python背景,但在我初次接触Go时,我发现slice比Go的切片更熟悉,因为我对Go感觉更舒服,我有回去把我的所有名单都改成了切片。我还没有找到map和/或list.List不允许我这样做的任何内容,因此我需要使用{{1}}。

答案 2 :(得分:9)

我认为这是因为对container/list套餐的解释并不多,因为{em>> 一旦你吸收了什么是主要的Go用于处理通用数据的习惯用法。

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

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

以下是here

的示例
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))
    }
}

在这里,我们使用e.Value()获取元素的值,然后将其声明为int原始插入值的类型。

您可以在&#34; Effective Go&#34;中阅读类型断言和类型开关。或任何其他介绍书。 container/list软件包的文档总结了所有方法列表支持。

答案 3 :(得分:4)

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

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

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

答案 4 :(得分:3)

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

Scott Meyer谈论缓存的重要性.. https://www.youtube.com/watch?v=WDIkqP4JbkE

答案 5 :(得分:2)

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


It depends a lot on the number of elements in your lists,
 whether a true list or a slice will be more efficient
 when you need to do many deletions in the 'middle' of the list.

#1
The more elements, the less attractive a slice becomes. 

#2
When the ordering of the elements isn't important,
 it is most efficient to use a slice and
 deleting an element by replacing it by the last element in the slice and
 reslicing the slice to shrink the len by 1
 (as explained in the SliceTricks wiki)

<强>因此
使用切片
1.如果列表中的元素顺序不重要,您需要删除,只需要 使用List交换元素以删除最后一个元素,&amp;重新切片到(长度-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.

<强>因此
使用切片
1.如果你需要遍历速度

答案 6 :(得分:2)

list.List被实现为双向链表。如果不经常插入列表中间,基于数组的列表(C ++中的向量或golang中的切片)在大多数情况下比链接列表更好。尽管数组列表必须扩展容量并复制现有值,但追加的分摊时间复杂度对于数组列表和链表都是O(1)。由于数据结构中没有指针,数组列表具有更快的随机访问速度,更小的内存占用,更重要的是对垃圾收集器友好。