2011-12-27 4 views
4

Я играю с ленивыми итераторами Scala, и у меня возникла проблема. То, что я пытаюсь сделать, это прочитать в большом файле, сделать преобразование, а затем записать результат:Scala Infinite Iterator OutOfMemory

object FileProcessor { 
    def main(args: Array[String]) { 
    val inSource = Source.fromFile("in.txt") 
    val outSource = new PrintWriter("out.txt") 

    try { 
     // this "basic" lazy iterator works fine 
     // val iterator = inSource.getLines 

     // ...but this one, which incorporates my process method, 
     // throws OutOfMemoryExceptions 
     val iterator = process(inSource.getLines.toSeq).iterator 

     while(iterator.hasNext) outSource.println(iterator.next) 

    } finally { 
     inSource.close() 
     outSource.close() 
    } 
    } 

    // processing in this case just means upper-cases every line 
    private def process(contents: Seq[String]) = contents.map(_.toUpperCase) 
} 

Так я получаю OutOfMemoryException на больших файлах. Я знаю, что вы можете столкнуться с ленивыми потоками Скалы, если будете придерживаться ссылок на главу Потока. Поэтому в этом случае я стараюсь преобразовать результат процесса() в итератор и отбросить Seq, который он первоначально возвращает.

Кто-нибудь знает, почему это все еще вызывает потребление памяти O (n)? Благодаря!


Update

В ответ на FGE и huynhjl, кажется, что послед может быть виновником, но я не знаю, почему. В качестве примера, следующий код работает отлично (и я использую Seq повсюду). Этот код делает не производить OutOfMemoryException:

object FileReader { 
    def main(args: Array[String]) { 

    val inSource = Source.fromFile("in.txt") 
    val outSource = new PrintWriter("out.txt") 
    try { 
    writeToFile(outSource, process(inSource.getLines.toSeq)) 
    } finally { 
    inSource.close() 
    outSource.close() 
    } 
} 

@scala.annotation.tailrec 
private def writeToFile(outSource: PrintWriter, contents: Seq[String]) { 
    if (! contents.isEmpty) { 
    outSource.println(contents.head) 
    writeToFile(outSource, contents.tail) 
    } 
} 

private def process(contents: Seq[String]) = contents.map(_.toUpperCase) 
+3

Дикая догадка: '.getLines.toSeq'? – fge

ответ

6

Как намекают FGE, модифицировать process принимать итератор и удалить .toSeq. inSource.getLines уже итератор.

Преобразование в Seq приведет к тому, что предметы будут запоминаться. Я думаю, что он преобразует итератор в Stream и заставит все предметы запомниться.

Редактировать: Хорошо, это более тонкий. Вы делаете эквивалент Iterator.toSeq.iterator, вызывая iterator по результатам процесса. Это может вызвать исключение из памяти.

scala> Iterator.continually(1).toSeq.iterator.take(300*1024*1024).size 
java.lang.OutOfMemoryError: Java heap space 

Это может быть та же самая проблема, описанная здесь: https://issues.scala-lang.org/browse/SI-4835. Обратите внимание на мой комментарий в конце ошибки, это из личного опыта.