これが何が起こるかです。Stream
常に遅延評価されますが、すでに計算された要素は後で使用できるように「キャッシュ」されます。遅延評価は非常に重要です。このコードを見てください:
a = a.flatMap( v => Some( v ) )
Stream
まるで別のものに変身し、古いものを捨てているように見えますが、そうではありません。新しいStream
ものはまだ古いものへの参照を保持しています。これは、結果Stream
が基になるストリームのすべての要素を積極的に計算するのではなく、必要に応じて計算する必要があるためです。これを例にとります:
io.Source.fromFile("very-large.file").getLines().toStream.
map(_.trim).
filter(_.contains("X")).
map(_.substring(0, 10)).
map(_.toUpperCase)
必要な数の操作を連鎖させることができますが、ファイルは最初の行を読み取るためにほとんど変更されません。後続の各操作はStream
、子ストリームへの参照を保持して、前の をラップするだけです。お願いしsize
たりした瞬間foreach
から評価が始まります。
コードに戻ります。2 回目の繰り返しでは、3 番目のストリームを作成し、2 番目のストリームへの参照を保持します。2 番目のストリームは、最初に定義したストリームへの参照を保持します。基本的に、かなり大きなオブジェクトのスタックが成長しています。
しかし、これはメモリ リークが非常に速く発生する理由を説明していません。重要な部分は... println()
、a.size
正確には。印刷しない (つまり全体を評価するStream
)と、「未評価Stream
」のままになります。未評価のストリームは値をキャッシュしないため、非常にスリムです。互いにストリームのチェーンが成長するため、メモリリークは依然として発生しますが、はるかに遅くなります。
これは疑問を投げかけます: なぜそれが動作するtoList
のか 非常に単純です。List.map()
新しい を熱心に作成しますList
。限目。以前のものは参照されなくなり、GC の対象となります。