Scala - итератор для всех строк в файлах в каталоге
Мне очень нравится
for (line <- Source fromFile inputPath getLines) {doSomething line}
конструкция для перебора файлов в scala, и мне интересно, есть ли способ использовать подобную конструкцию для перебора строк во всех файлах в каталоге.
Важным ограничением здесь является то, что все файлы складываются в объем пространства, которое может привести к переполнению кучи. (Подумайте о десятках ГБ, поэтому увеличение размера кучи не вариант). В настоящее время я работаю вокруг, собирая все вместе в один файл и используя описанную выше конструкцию, которая работает b/c из-за лени.
Суть в том, что это, кажется, поднимает вопросы вроде... могу ли я объединить два (сто) ленивых итераторов и получить действительно большой, действительно ленивый?
1 ответ
Да, хотя это не совсем так кратко:
import java.io.File
import scala.io.Source
for {
file <- new File(dir).listFiles.toIterator if file.isFile
line <- Source fromFile file getLines
} { doSomething line }
Хитрость в том, flatMap
и егоfor
понимание синтаксического сахара. Выше, например, более или менее эквивалентно следующему:
new File(dir)
.listFiles.toIterator
.filter(_.isFile)
.flatMap(Source fromFile _ getLines)
.map(doSomething)
Как отмечает Даниэль Собрал в комментарии ниже, этот подход (и код в вашем вопросе) оставит файлы открытыми. Если это одноразовый скрипт или вы просто работаете в REPL, это может не иметь большого значения. Если у вас возникнут проблемы, вы можете использовать шаблон pimp-my-library для реализации некоторого базового управления ресурсами:
implicit def toClosingSource(source: Source) = new {
val lines = source.getLines
var stillOpen = true
def getLinesAndClose = new Iterator[String] {
def hasNext = stillOpen && lines.hasNext
def next = {
val line = lines.next
if (!lines.hasNext) { source.close() ; stillOpen = false }
line
}
}
}
Теперь просто используйте Source fromFile file getLinesAndClose
и вам не придется беспокоиться о том, что файлы остаются открытыми.