Scala 的大多数集合都急切地应用操作,并且(除非您使用的是为您执行此操作的宏库)不会融合操作。所以filter 后跟map 通常会创建两个集合(即使你使用Iterator 或类似的东西,中间形式也会暂时创建,尽管一次只创建一个元素),而collect 不会。
另一方面,collect 使用偏函数来实现联合测试,偏函数在测试集合中是否存在某些东西时比谓词 (A => Boolean) 慢。
此外,在某些情况下,读取一个比另一个更清晰,并且您不关心性能或内存使用差异 2 倍左右。在这种情况下,使用更清晰的那个。一般来说,如果你已经命名了函数,阅读起来会更清晰
xs.filter(p).map(f)
xs.collect{ case x if p(x) => f(x) }
但如果您内联提供闭包,collect 通常看起来更干净
xs.filter(x < foo(x, x)).map(x => bar(x, x))
xs.collect{ case x if foo(x, x) => bar(x, x) }
即使它不一定更短,因为您只引用了一次变量。
现在,性能差异有多大?这会有所不同,但如果我们考虑这样的集合:
val v = Vector.tabulate(10000)(i => ((i%100).toString, (i%7).toString))
并且你想根据过滤第一个条目来挑选第二个条目(所以过滤和映射操作都很容易),那么我们得到下表。
注意:可以将惰性视图放入集合中并在那里收集操作。您并不总能找回原来的类型,但您始终可以使用 to 获得正确的集合类型。因此,xs.view.filter(p).map(f).toVector 会因为视图而不会创建中间体。这也在下面进行了测试。也有人建议可以xs.flatMap(x => if (p(x)) Some(f(x)) else None),这是高效。 事实并非如此。下面也进行了测试。并且可以通过显式创建一个构建器来避免偏函数:val vb = Vector.newBuilder[String]; xs.foreach(x => if (p(x)) vb += f(x)); vb.result,其结果也在下面列出。
在下表中,测试了三个条件:什么都不过滤、过滤一半、什么都过滤。时间已标准化为过滤器/映射(100% = 与过滤器/映射相同的时间,越低越好)。误差范围约为 +- 3%。
不同过滤器/映射替代方案的性能
====================== Vector ========================
filter/map collect view filt/map flatMap builder
100% 44% 64% 440% 30% filter out none
100% 60% 76% 605% 42% filter out half
100% 112% 103% 1300% 74% filter out all
因此,filter/map 和 collect 通常非常接近(当您保留很多时,collect 获胜),flatMap 在所有情况下都慢得多,并且创建构建器总是获胜。 (Vector 尤其如此。其他集合可能有一些不同的特征,但大多数的趋势会相似,因为操作上的差异是相似的。)this 中的视图em> 测试往往是一个胜利,但它们并不总是无缝地工作(而且它们并不比collect 更好,除了空的情况)。
所以,底线:如果在速度无关紧要时有助于清晰度,则更喜欢filter 然后map,或者当您过滤掉几乎所有内容但仍想保持功能正常时,更喜欢它以提高速度(所以不要'不想使用构建器);否则使用collect。