Ваш код должен работать, но он довольно неэффективен в использовании памяти: вы читаете весь файл в память, затем тратите больше памяти и обработки, размещая строки в правильном порядке.
Использование метода Source.fromFile
в стандартной библиотеке - ваша лучшая ставка (которая также поддерживает различные кодировки файлов), как указано в других комментариях / ответах.
Однако, если вам нужно бросить свой, я думаю, использование Stream
( lazy форма списка) имеет больше смысла, чем List
. Вы можете возвращать каждую строку по одной за раз, и можете завершить поток, когда достигнут конец файла. Это можно сделать следующим образом:
import java.io.{BufferedReader, FileReader}
def linesFromFile(file: String): Stream[String] = {
// The value of buffer is available to the following helper function. No need to pass as
// an argument.
val buffer = new BufferedReader(new FileReader(file))
// Helper: retrieve next line from file. Called only when next value requested.
def materialize: Stream[String] = {
// Uncomment to demonstrate non-recursive nature of this method.
//println("Materialize called!")
// Read the next line and wrap in an option. This avoids the hated null.
Option(buffer.readLine) match {
// If we've seen the end of the file, return an empty stream. We're done reading.
case None => {
buffer.close()
Stream.empty
}
// Otherwise, prepend the line read to another call to this helper.
case Some(line) => line #:: materialize
}
}
// Start the process.
materialize
}
Хотя похоже, что materialize
является рекурсивным, на самом деле он вызывается только тогда, когда нужно получить другое значение, поэтому вам не нужно беспокоиться о переполнении стека или рекурсии. Вы можете проверить это, раскомментировав вызов println
.
Например (в сеансе Scala REPL ):
$ scala
Welcome to Scala 2.12.5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_171).
Type in expressions for evaluation. Or try :help.
scala> import java.io.{BufferedReader, FileReader}
import java.io.{BufferedReader, FileReader}
scala> def linesFromFile(file: String): Stream[String] = {
|
| // The value of buffer is available to the following helper function. No need to pass as
| // an argument.
| val buffer = new BufferedReader(new FileReader(file))
|
| // Helper: retrieve next line from file. Called only when next value requested.
| def materialize: Stream[String] = {
|
| // Uncomment to demonstrate non-recursive nature of this method.
| println("Materialize called!")
|
| // Read the next line and wrap in an option. This avoids the hated null.
| Option(buffer.readLine) match {
|
| // If we've seen the end of the file, return an empty stream. We're done reading.
| case None => {
| buffer.close()
| Stream.empty
| }
|
| // Otherwise, prepend the line read to another call to this helper.
| case Some(line) => line #:: materialize
| }
| }
|
| // Start the process.
| materialize
| }
linesFromFile: (file: String)Stream[String]
scala> val stream = linesFromFile("TestFile.txt")
Materialize called!
stream: Stream[String] = Stream(Line 1, ?)
scala> stream.head
res0: String = Line 1
scala> stream.tail.head
Materialize called!
res1: String = Line 2
scala> stream.tail.head
res2: String = Line 2
scala> stream.foreach(println)
Line 1
Line 2
Materialize called!
Line 3
Materialize called!
Line 4
Materialize called!
Обратите внимание, как materialize
вызывается только когда мы пытаемся прочитать другую строку из файла. Кроме того, он не вызывается, если мы уже получили строку (например, Line 1
и Line 2
в выходных данных предшествуют Materialize called!
при первой ссылке).
К вашей точке зрения о пустых файлах, в этом случае возвращается пустой поток:
scala> val empty = linesFromFile("EmptyFile.txt")
Materialize called!
empty: Stream[String] = Stream()
scala> empty.isEmpty
res3: Boolean = true