为什么在Go中很less使用列表?
我对Go很陌生,对此很兴奋。 但是,在所有我已经广泛使用的语言中:Delphi,C#,C ++,Python – 列表非常重要,因为它们可以dynamicresize,而不是数组。
在Golang中,确实有一个list.List
结构体,但是我看不到有关它的文档 – 无论是在Go By Example还是三个Go书(Summerfield,Chisnal和Balbaert)中,他们都花费了大量的时间然后切片,然后跳到地图。 在souce代码的例子中,我也很less或没有使用list.List
。
它似乎也不像Python, Range
不支持List – 大缺点IMO。 我错过了什么吗?
切片当然是很好的,但他们仍然需要基于硬编码大小的arrays。 这就是List进来的地方。有没有一种方法可以在Go中创build一个数组/片段而不需要硬编码的数组大小? 为什么列表被忽略?
当你想要一个列表的时候,总是使用一个切片而不是Go。 切片dynamic地重新resize。 在他们的底下是可以改变大小的连续的记忆片段。
如果您阅读SliceTricks wiki页面 ,您会发现它们非常灵活。
这是一个摘录:
复制
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]
stream行的
x, a = a[len(a)-1], a[:len(a)-1]
推
a = append(a, x)
更新 :这是一个链接到博客文章的所有关于从去团队本身,这很好地解释了片和数组和片内部之间的关系切片。
几个月前我问这个问题的时候,我刚开始调查Go的时候。 从那以后,我每天都在阅读关于Go的内容,并在Go中进行编码。
因为我没有得到这个问题的明确答案(尽pipe我已经接受了一个答案),我现在要根据我所学到的来自己回答,因为我问了这个问题:
有没有一种方法来创build一个数组/切片没有硬编码数组大小?
是。 切片不需要硬编码的数组来slice
:
var sl []int = make([]int,len,cap)
这段代码分配的slice sl
,其大小为len
,容量为cap
– len
, cap
是可以在运行时分配的variables。
为什么
list.List
被忽略?
看来主要原因list.List
在Go中似乎没有得到重视的是:
-
正如在@Nick Craig-Wood的回答中所解释的那样,使用片断无法完成的列表几乎没有任何事情可做,通常更有效率,更清晰,更优雅的语法。 例如范围构造:
for i:=range sl { sl[i]=i }
不能与列表一起使用 – 一个C风格的循环是必需的。 在许多情况下,C ++集合样式语法必须与列表一起使用:
push_back
等 -
也许更重要的是,
list.List
不是强types的 – 它与Python的列表和字典非常相似,它允许在集合中混合各种types。 这似乎与Go的做法相反。 Go是一种非常强大的types化语言 – 例如,在Go中永远不允许隐式types转换,甚至从int
到int64
的upCast都必须是显式的。 但是list.List的所有方法都是空的接口 – 什么都可以。我放弃Python并转到Go的原因之一是由于Pythontypes系统的这种弱点,虽然Python声称是“强types”(IMO不是)。 Go的
list.List
似乎是一种由C ++的vector<T>
和Python的List()
的“mongrel”,在Go本身中也许有点list.List
。
如果在不久的将来某个时候,我们会发现list.List在Go中被弃用,尽pipe也许它仍然存在,以适应罕见的情况,即使使用良好的devise实践,也能最好地解决问题与持有各种types的集合。 或者,也许是为了让C系列开发人员在学习Go,AFAIK独有的切片的细微差别之前为Go提供舒适的“桥梁”。 (在某些方面,切片看起来类似于C ++或Delphi中的stream类,但并不完全)。
尽pipe来自Delphi / C ++ / Python背景,但是在我最初接触Go的时候,我发现list.List
比Go的切片更为熟悉,因为我对Go更加熟悉,所以我已经把所有的列表改为切片。 我还没有发现任何slice
和/或map
不允许我做,所以我需要使用list.List
。
我认为这是因为没有太多的关于他们的说法,因为一旦你吸收了通用数据处理的主要成语,那么container/list
包就不言自明了。
在Delphi(没有generics)或C中,你可以将指针或TObject
存储在列表中,然后在从列表中获取时将它们转换回真实types。 在C ++中,STL列表是模板,因此按types进行参数化,而在C#中(现在)列表是通用的。
在Go中, container/list
存储了types为interface{}
值,它是一种能够表示任何其他(真实)types的值的特殊types的值 – 通过存储一对指针:一个指向包含值的types信息,一个指针的值(或直接的值,如果它的大小不大于指针的大小)。 所以当你想添加一个元素到列表中时,你只需要将它作为interface{}
函数参数来接受任何types的值。 但是当你从列表中提取值时,以及如何处理它们的实际types,你必须键入它们,或者在它们上面进行types切换 ,这两种方法基本上是完全相同的。
这里是一个从这里取得的例子:
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()
获得一个元素的值,然后将其作为原始插入值的types的int
types声明。
您可以在“Effective Go”或任何其他介绍手册中阅读types断言和types开关。 container/list
包的文档汇总了所有的方法列表支持。
请注意,Go切片可以通过append()
内build函数进行扩展。 虽然这有时需要复制后备数组,但不会每次都发生,因为Go会超出新数组的大小,使得数组容量大于报告长度。 这意味着后续的追加操作可以在没有其他数据拷贝的情况下完成。
虽然结束了更多的数据副本,而不是使用链接列表实现的等效代码,但您不必单独分配列表中的元素,而需要更新“ Next
指针。 对于许多用途来说,基于数组的实现提供了更好或更好的性能,所以这是语言中强调的。 有趣的是,Python的标准list
types也是数组支持的,并且在附加值时具有类似的性能特征。
也就是说,在某些情况下,链表是一个更好的select(例如,当你需要插入或从长列表的中间插入元素时),这就是为什么提供标准库实现的原因。 我想他们没有添加任何特殊的语言function来处理它们,因为这些情况不如那些使用切片的情况。
除非切片更新方式太频繁(删除,随机位置添加元素),切片的内存连续性将提供优良的caching命中率与链表相比。
Scott Meyer谈到caching的重要性.. https://www.youtube.com/watch?v=WDIkqP4JbkE
来自: https : //groups.google.com/forum/#!msg/golang-nuts/mPKCoYNwsoU/tLefhE7tQjMJ
这取决于列表中元素的数量, 无论是一个真正的清单还是一个切片都会更有效率 当你需要在列表的“中间”做很多删除。 #1 越多的元素,切片越不具吸引力。 #2 当元素的sorting不重要时, 使用切片和切片是最有效的 通过将其replace为切片中的最后一个元素来删除元素 将这个切片重新拼成一个len (如SliceTricks维基解释)
所以
使用切片
1.如果列表中元素的顺序不重要,则需要删除
使用List交换要删除的元素与最后一个元素,并重新切片(长度为1)
2.当元素更多(无论更多的手段)
There are ways to mitigate the deletion problem -- eg 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.如果你需要遍历速度