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

时间:2011-08-03 14:45:08

标签: scala vector scala-collections

似乎Vector迟到了Scala收集派对,并且所有有影响力的博客帖子已经离开了。

在Java中ArrayList是默认集合 - 我可能会使用LinkedList,但只有在我考虑算法并且足够关注优化时才会使用Vector。在Scala中我应该使用Seq作为我的默认List,还是在{{1}}实际上更合适时尝试解决?

6 个答案:

答案 0 :(得分:255)

作为一般规则,默认使用Vector。对于几乎所有内容,它比List更快,对于大于平凡的大小序列,它的内存效率更高。请参阅此documentation与其他集合相比的Vector的相对性能。与Vector一起使用会有一些缺点。具体做法是:

  • 头部的更新速度比List慢(虽然没有你想象的那么多)

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

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

如果我有一个List[A]类型的序列,我实际上断言了两件事。首先,我的算法(和数据)完全是堆栈结构的。其次,我断言我将要对这个集合做的唯一事情是完整的O(n)遍历。这两者真的是相辅相成的。相反,如果我有Vector[A]类型的东西,我断言的事情是我的数据具有明确定义的顺序和有限长度。因此,Vector的断言较弱,这导致其更大的灵活性。

答案 1 :(得分:87)

如果算法可以仅使用List::head来实现,那么tail可能会非常快。我最近有一个对象课,当我通过生成split而不是List来击败Java Array时,并且无法用其他任何东西击败它。

但是,List存在一个基本问题:它不适用于并行算法。我无法以有效的方式将List拆分成多个段,或者将其连接回来。

还有其他类型的集合可以更好地处理并行性 - 而Vector就是其中之一。 Vector也有很好的位置 - List没有 - 这对于某些算法来说可能是一个真正的优势。

所以,考虑到所有事情,Vector的最佳选择,除非您有特定的注意事项,使其他一个集合更受欢迎 - 例如,您可以选择{{1}如果你想要延迟评估和缓存(Stream更快但不缓存),或Iterator如果算法自然地用我提到的操作实现。

顺便说一句,除非您需要特定的API(例如List的{​​{1}}),否则最好使用SeqIndexedSeq,甚至如果您的算法可以并行运行,则List::

答案 2 :(得分:21)

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

对于IndexedSeq,您通常会选择VectorRangeWrappedString也是IndexedSeqs。

对于LinearSeq,您通常会选择List或其懒惰的等效Stream。其他示例包括QueueStack s。

因此,在Java术语中,ArrayList与Scala的Vector类似,而LinkedList与Scala的List类似。但是在Scala中,我倾向于比Vector更频繁地使用List,因为Scala对包含遍历序列的函数(如映射,折叠,迭代等)有更好的支持。您将倾向于使用这些函数来操作列表作为整体而不是随机访问个别元素。

答案 3 :(得分:19)

这里的一些陈述令人困惑甚至错误,尤其是Scala中的immutable.Vector类似于ArrayList的想法。 List和Vector都是不可变的,持久的(即#34;便宜以获得修改后的副本")数据结构。 没有合理的默认选择,因为它们可能适用于可变数据结构,但它取决于您的算法正在做什么。 List是单链表,而Vector是base-32整数trie,即它是一种具有32级节点的搜索树。 使用这种结构,Vector可以合理地快速地提供最常见的操作,即在O(log_32(n))中。这适用于前置,后置,更新,随机访问,头/尾分解。按顺序迭代是线性的。 另一方面,List仅提供线性迭代和恒定时间前置,头/尾分解。其他一切都需要一般的线性时间。

这看起来好像Vector几乎在所有情况下都是List的良好替代品,但是前置,分解和迭代通常是功能程序中序列的关键操作,并且这些操作的常量(更高)对于矢量,由于其更复杂的结构。 我进行了一些测量,因此迭代速度大约是列表的两倍,前缀在列表上快了大约100倍,头/尾的分解在列表上快了大约10倍,而从可遍历的生成大约是向量的2倍。 (这可能是因为Vector在使用构建器构建它时可以一次分配32个元素的数组,而不是逐个添加或附加元素)。 当然,所有在列表上采用线性时间但在向量上有效恒定时间(如随机访问或追加)的操作在大型列表上会非常慢。

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

  • 我们只需要通过map,filter,fold等操作来转换序列: 基本上没关系,我们应该统一编程我们的算法,甚至可以从接受并行序列中受益。对于顺序操作,List可能会更快一些。但如果你必须进行优化,你应该对它进行基准测试。
  • 我们需要大量的随机访问和不同的更新,所以我们应该使用vector,list会非常慢。
  • 我们以经典的函数方式对列表进行操作,通过递归分解和迭代来构建它们:使用list,vector将慢10-100或更多。
  • 我们有一个性能关键的算法,它基本上是必不可少的,并且在列表上进行了大量的随机访问,类似于快速排序:使用命令式数据结构,例如ArrayBuffer,在本地并从中复制数据。

答案 4 :(得分:2)

在涉及大量随机访问和随机变异的情况下,Vector(或 - docs说 - a Seq)似乎是一个很好的折衷方案。这也是performance characteristics建议的内容。

此外,Vector类似乎在分布式环境中运行良好,没有太多数据重复,因为不需要为整个对象执行写时复制。 (见:http://akka.io/docs/akka/1.1.3/scala/stm.html#persistent-datastructures

答案 5 :(得分:0)

如果你是不可变的编程并且需要随机访问,那么Seq就是你要去的方法(除非你想要一个你经常做的Set)。否则List工作正常,但它的操作无法并行化。

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