这两个表达式应该是一样的:
Stream.from(1).filter(_ < 0).head
Stream.from(1).find(_ < 0)
应该循环,直到它们返回Int.MinValue
。这正是filter
的版本所做的,但find
生成了OutOfMemoryError
。不过看一下它们的实现,我无法弄清楚这两个版本都不会产生OutOfMemoryError
。
以下是Stream.filter
的实施:
override def filter(p: A => Boolean): Stream[A] = {
// optimization: drop leading prefix of elems for which f returns false
// var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise
var rest = this
while (!rest.isEmpty && !p(rest.head)) rest = rest.tail
// private utility func to avoid `this` on stack (would be needed for the lazy arg)
if (rest.nonEmpty) Stream.filteredTail(rest, p)
else Stream.Empty
}
find
继承自LinearSeqOptimized
,其定义为:
override /*IterableLike*/
def find(p: A => Boolean): Option[A] = {
var these = this
while (!these.isEmpty) {
if (p(these.head)) return Some(these.head)
these = these.tail
}
None
}
它们都有一个while循环,它丢弃不满足谓词的Stream
元素。因为this
应该保持对Stream
开头的引用,所有这些创建的元素应该在内存中累积,直到我们用完空间。除非我真的误解了这里发生的事情,Stream.filter
在进入while循环之前以某种方式从堆栈帧中消除this
。 Stream.filter
中关于未使用dropWhile
原因的评论看起来像是一个暗示,但我不知道它是指什么。
我的下一步是学习如何反汇编和读取JVM字节码,但我真的希望有人知道这里发生了什么。
答案 0 :(得分:3)
它是HotSpot和Scala的特征实现方式的结合。
如果我使用-Xint
关闭HotSpot,Stream.filter
也会因OutOfMemoryException
而死亡。在生成的字节码本身中,this
和变量rest
和these
存储在不同的内存位置,但由于this
仅用于初始化这些变量,我相信HotSpot是足够聪明,可以简单地重用this
的内存位置。这解释了为什么Stream.filter
不会耗尽内存。
HotSpot对Stream.filter
的优化也应适用于LinearSeqOptimized.find
,但由于实施了特征的方式,因此会保留对this
的引用。当在特征内部实现方法时,Scala将该方法编译为静态方法。当一个类继承该特征时,Scala会创建一个调用静态方法的小型存根方法。因此,即使HotSpot优化LinearSeqOptimized.find
的静态方法,存根方法的堆栈帧仍然引用this
。