意外的Scala集合内存行为
以下
Scala代码(在2.9.2上):
var a = ( 0 until 100000 ).toStream for ( i <- 0 until 100000 ) { val memTot = Runtime.getRuntime().totalMemory().toDouble / ( 1024.0 * 1024.0 ) println( i,a.size,memTot ) a = a.map(identity) } 在循环的每次迭代中使用不断增加的内存量.如果a被定义为(0到100000).toList,则内存使用量是稳定的(给予或采用GC). 我理解流可以懒惰地评估,但一旦生成就保留元素.但似乎在上面的代码中,每个新流(由最后一行代码生成)以某种方式保留对先前流的引用.有人可以解释一下吗? 解决方法
这是发生了什么.流总是被懒惰地评估,但已经计算的元素被“缓存”以供以后使用.懒惰的评估至关重要.看看这段代码:
a = a.flatMap( v => Some( v ) ) 虽然看起来好像你正在将一个Stream转换为另一个Stream而丢弃旧的Stream,但事实并非如此.新的Stream仍然保留对旧的Stream的引用.这是因为结果Stream不应急切地计算底层流的所有元素,而是按需执行.以此为例: io.Source.fromFile("very-large.file").getLines().toStream. map(_.trim). filter(_.contains("X")). map(_.substring(0,10)). map(_.toUpperCase) 您可以根据需要链接任意数量的操作,但只读取第一行文件.每个后续操作只包装前一个Stream,保存对子流的引用.当你要求大小或做foreach时,评估开始. 回到你的代码.在第二次迭代中,您创建第三个流,保持对第二个流的引用,该引用依次保留对您最初定义的引用的引用.基本上你有一堆相当大的物体在增长. 但这并不能解释为什么内存泄漏如此之快.关键部分是… println(),或者说是精确的a.size.没有打印(从而评估整个流)流仍然是“未评估”.未评估的流不会缓存任何值,因此它非常小.由于彼此之间的流链不断增长,内存仍会泄漏,但速度要慢得多. 这引出了一个问题:为什么它与toList一起使用它非常简单. List.map()急切地创建新的List.期.之前的一个不再被引用并且符合GC的条件. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |