Чтение пустых файлов с помощью Scala - PullRequest
0 голосов
/ 02 мая 2018

Я написал следующую функцию в scala, которая считывает файл в список строк. Моя цель - убедиться, что если входные данные файла пустые, возвращаемый список тоже пуст. Любая идея, как это сделать элегантно:

def linesFromFile(file: String): List[String] = {

  def materialize(buffer: BufferedReader): List[String] = materializeReverse(buffer, Nil).reverse

  def materializeReverse(buffer: BufferedReader, accumulator: List[String]): List[String] = {
    buffer.readLine match {
      case null => accumulator
      case line => materializeReverse(buffer, line :: accumulator)
    }
  }

  val buffer = new BufferedReader(new FileReader(file))
  materialize(buffer)
}

Ответы [ 3 ]

0 голосов
/ 02 мая 2018

Вы можете попробовать этот метод

val result = Source.fromFile("C:\\Users\\1.txt").getLines.toList

Надеюсь, это поможет. Пожалуйста, спросите, если вам нужны дальнейшие разъяснения.

0 голосов
/ 02 мая 2018

Ваш код должен работать, но он довольно неэффективен в использовании памяти: вы читаете весь файл в память, затем тратите больше памяти и обработки, размещая строки в правильном порядке.

Использование метода 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
0 голосов
/ 02 мая 2018

Если под «пустым» вы имеете в виду абсолютно ничего, ваша цель уже достигнута.

Если, однако, вы имели в виду «содержит только пробелы», вы можете отфильтровать строки, содержащие только пробелы, из вашего окончательного списка, изменив materializeReverse.

def materializeReverse(buffer: BufferedReader, accumulator: List[String]): List[String] = {
    buffer.readLine match {
        case null => accumulator
        case line if line.trim.isEmpty => materializeReverse(buffer, accumulator)
        case line => materializeReverse(buffer, line :: accumulator)
    }
}
...