什么时候应该在Scala中选择Vector?


200

似乎Vector对Scala收藏晚会来得太晚了,所有有影响力的博客文章都已经离开了。

在Java ArrayList中,默认集合是-我可能会使用,LinkedList但仅当我考虑了算法并足够谨慎地优化时才使用。在Scala中,我应该将其Vector用作默认值Seq,还是尝试找出List实际上更合适的时间?


1
我想我的意思是,在Java中,我将创建编写List<String> l = new ArrayList<String>()Scala博客,让您相信每个人都可以使用List来获得持久的收集好处-但是Vector是否足够通用,我们应该在List的地方使用它?
邓肯·麦格雷戈

9
@Debilski:我想知道你的意思。List当我Seq()在REPL上打字时,我得到一个。
missingfaktor 2011年

1
嗯,它在文档中是这样说的。也许这仅适用于IndexedSeq
Debilski

1
关于默认具体类型的评论Seq已使用了三年以上。从Scala 2.11.4(及更低版本)开始,默认的具体类型SeqList
马克·坎拉斯2014年

3
对于随机访问,矢量更好。对于头尾访问,列表更好。对于批量操作(例如地图,过滤器),矢量是首选,因为矢量以32个元素作为一个块组织在一起,而列表以相互指向指针的形式组织了元素,因此不能保证这些元素彼此靠近。
johnsam'9

Answers:


280

通常,默认使用Vector。这是比快List几乎一切,更多的内存效率比平凡的较大尺寸的序列。请参阅此文档,了解Vector与其他集合相比的相对性能。与之相关的还有一些缺点Vector。特别:

  • 头部的更新慢于List(尽管不如您想象的那么慢)

在Scala 2.10之前的另一个缺点是模式匹配支持更好List,但是在2.10中使用通用+::+提取器进行了纠正。

解决这个问题还有一种更抽象的代数方式:从概念上讲,您有什么样的序列?另外,您在概念上在做什么?如果看到返回的函数,则Option[A]知道该函数在其域中有一些漏洞(因此是局部的)。我们可以将同样的逻辑应用于集合。

如果我有一个type序列List[A],那么我实际上是在断言两件事。首先,我的算法(和数据)完全是堆栈结构的。第二,我断言我将要使用此集合进行的所有操作都是完整的O(n)遍历。这两个真的并存。相反,如果我有一个类型的东西Vector[A]时,只有我主张的是,我的数据有一个定义良好的顺序和长度有限。因此,使用的断言较弱Vector,这导致其更大的灵活性。


2
2.10已经推出了一段时间,列表模式匹配是否仍然比Vector更好?
蒂姆·高

3
列表模式匹配不再更好。实际上,恰恰相反。例如,要获得头和尾,可以做到case head +: tailcase tail :+ head。要与空匹配,可以这样做case Seq()。API中提供了您所需的一切,它比List的通用性强
Kai Sellgren 2014年

List用单链表实现。Vector实现类似于Java的ArrayList
Josiah Yoder 2015年

6
@JosiahYoder它没有像ArrayList那样实现。ArrayList包装它动态调整大小的数组。Vector是一个trie,其中键是值的索引。
John Colanduoni 2015年

1
我道歉。我正在浏览一个对细节含糊的网络资源。我应该更正我以前的说法吗?还是那不好的形式?
Josiah Yoder 2015年

93

好了,List可以非常快,如果该算法可以单独与实施::head以及tail。最近有一个关于对象的课程,当我split通过生成a List而不是来击败Java的时候Array,并且在其他方​​面无法胜任。

但是,List存在一个基本问题:它不适用于并行算法。我无法List以有效的方式将a 分为多个部分,也无法将其串联起来。

还有其他种类的集合可以更好地处理并行性- Vector就是其中之一。Vector也具有很大的局部性- List并非如此-对于某些算法来说这可能是真正的优点。

因此,所有的事情考虑,Vector是最好的选择,除非你有特殊的考虑,最好做其他的收藏品之一-例如,你可以选择Stream,如果你想偷懒的评价和缓存(Iterator速度更快,但是不缓存),或者List如果该算法自然是通过我提到的操作实现的。

顺便说一句,最好是使用SeqIndexedSeq除非你想API的特定部分(如List::),甚至GenSeq或者GenIndexedSeq如果你的算法可以并行运行。


3
感谢你的回答。您所说的“地理位置优越”是什么意思?
Ngoc Dao

10
@ngocdaothanh意味着将数据在内存中紧密地分组在一起,从而在需要时增加了数据在缓存中的机会。
Daniel C. Sobral 2012年

1
@ user247077是的,鉴于我提到的细节,列表在性能上可以胜过Vectors。并非向量的所有动作均摊销O(1)。实际上,在不可变的数据结构上(这种情况),两端的交替插入/删除根本不会摊销。在这种情况下,缓存是无用的,因为您总是在复制向量。
Daniel C. Sobral 2014年

1
@ user247077也许您不知道VectorScala中的数据结构是不可变的?
Daniel C. Sobral 2014年

1
@ user247077比这复杂得多,包括一些内部可变的东西以使追加变得便宜,但是当您将其用作堆栈(这是不可变的列表最佳方案)时,您仍然最终拥有与链接列表相同的内存特征,但是具有更大的内存分配配置文件。
Daniel C. Sobral 2014年

29

这里的某些陈述令人困惑甚至错误,尤其是Scala中的immutable.Vector这样的想法,类似于ArrayList。List和Vector都是不变的,持久的(即“廉价获得修改后的副本”)数据结构。对于可变数据结构,没有合理的默认选择,因为它们可能取决于算法的工作。List是一个单链表,而Vector是一个基数为32的整数trie,即它是一种节点数为32的搜索树。使用这种结构,Vector可以相当快速地提供最常见的操作,即在O(log_32( n))。可以在头/尾进行前置,追加,更新,随机访问,分解。顺序迭代是线性的。另一方面,List仅提供线性迭代和恒定时间前缀,头/尾分解。

在几乎所有情况下,似乎Vector都可以很好地替代List,但是前置,分解和迭代通常是功能程序中序列上的关键操作,并且由于矢量,这些操作的常数要高得多使其结构更加复杂。我进行了一些测量,因此列表的迭代速度约为列表的两倍,前缀在列表上的速度约为100倍,头/尾的分解在列表上的速度约为10倍,而从可遍历生成的向量的速度约为2倍。(这可能是因为,当您使用构建器构建Vector时,Vector可以一次分配32个元素的数组,而不是一个一个地添加或添加元素)。

那么我们应该使用哪种数据结构?基本上,有四种常见情况:

  • 我们只需要通过map,filter,fold等操作来变换序列:基本上没关系,我们应该对算法进行通用编程,甚至可以从接受并行序列中受益。对于顺序操作,列表可能要快一些。但是,如果必须进行优化,则应该对其进行基准测试。
  • 我们需要大量的随机访问和不同的更新,因此我们应该使用向量,列表会非常慢。
  • 我们以经典的功能性方式对列表进行操作,通过在列表之前进行构建并通过递归分解进行迭代:使用列表,向量的速度将降低10-100倍甚至更多。
  • 我们有一个性能至关重要的算法,该算法基本上是命令性的,并且对列表进行大量随机访问,就像就地快速排序一样:在本地使用命令性数据结构(例如ArrayBuffer),然后在其中复制数据。

24

对于不可变的集合,如果需要序列,则主要决定是使用IndexedSeq还是LinearSeq,这为性能提供了不同的保证。IndexedSeq提供元素的快速随机访问和快速的长度操作。LinearSeq仅通过提供对第一个元素的快速访问head,而且tail操作快速。(摘自Seq文档。)

对于,IndexedSeq您通常会选择VectorRanges和WrappedStrings也是IndexedSeqs。

对于a,LinearSeq您通常会选择a List或它的等效项Stream。其他示例是Queues和Stacks。

因此,在Java术语,ArrayList使用同样Scala的Vector,并且LinkedList同样Scala的List。但是在Scala中,我倾向于使用列表而不是使用Vector,因为Scala对包括遍历序列的函数(如映射,折叠,迭代等)有更好的支持。您将倾向于使用这些函数将列表作为整体,而不是随机访问各个元素。


但是,如果Vector的迭代比List的迭代快,并且我也可以映射折叠等,那么除了某些特殊情况(本质上是所有专门针对List的FP算法)之外,List似乎本质上是遗留的。
邓肯·麦格雷戈

@Duncan您在哪里听说Vector的迭代速度更快?首先,您需要跟踪并更新当前索引,而无需使用链表。我不会将列表函数称为“特殊情况”-它们是函数式编程的基础。不使用它们就像在没有for或while循环的情况下尝试对Java进行编程。
2011年

2
我敢肯定Vector的迭代快,但需要有人基准它是肯定的。
Daniel Spiewak

我认为(?)元素以Vector32个一组的形式物理上存在于RAM上,它们更完全适合CPU缓存...因此,缓存未命中

2

其中涉及大量的随机访问和随机突变,情况一Vector(或-作为文档说-一Seq)似乎是一个很好的妥协。这也是性能特征所暗示的。

同样,Vector该类似乎在没有大量数据重复的分布式环境中也能很好地发挥作用,因为不需要对整个对象进行写时复制。(请参阅:http : //akka.io/docs/akka/1.1.3/scala/stm.html#persistent-datastructures


1
有很多东西要学习... Vector是默认的Seq是什么意思?如果我写Seq(1、2、3),我得到的是List [Int]而不是Vector [Int]。
Duncan McGregor

2
如果您具有随机访问权限,请使用IndexedSeq。这也是Vector,但这是另一回事。
Daniel C. Sobral

@DuncanMcGregor:Vector是IndexedSeq实现的默认值SeqSeq(1, 2, 3)LinearSeq使用实现的List
pathikrit

0

如果您不可变地进行编程并且需要随机访问,则可以使用Seq(除非您想要Set,而您通常经常这样做)。否则List可以很好地工作,除非它的操作不能并行化。

如果不需要不变的数据结构,请坚持使用ArrayBuffer,因为它与ArrayList等效。


我坚持不变的,持久的集合的领域。我的观点是,即使我不需要随机访问,Vector能否有效地取代List?
邓肯·麦格雷戈

2
取决于用例。向量更加平衡。迭代比列表快,随机访问要快得多。更新速度较慢,因为它不仅是列表的开头,除非它是可通过构建器完成的大批量更新。就是说,我认为Vector是最好的默认选择,因为它用途广泛。
约书亚·哈特曼

我认为这是我的问题的核心-向量是如此之好,以至于在示例通常显示List的地方也可以使用它们。
Duncan McGregor
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.