Читать весь файл в Scala?

291

Какой простой и канонический способ прочитать весь файл в памяти в Scala? (В идеале, с контролем кодировки символов.)

Лучшее, что я могу придумать, это:

scala.io.Source.fromPath("file.txt").getLines.reduceLeft(_+_)

или я должен использовать одну из божественных идиом Java-богов, лучший из которых (без использования внешней библиотеки) выглядит следующим образом:

import java.util.Scanner
import java.io.File
new Scanner(new File("file.txt")).useDelimiter("\\Z").next()

Из чтения обсуждений списка рассылки мне не ясно, что scala.io.Source даже должен быть канонической библиотекой ввода-вывода. Я не понимаю, какова его цель.

... Мне хотелось бы что-то мертвое - просто и легко запомнить. Например, на этих языках очень сложно забыть идиому...

Ruby    open("file.txt").read
Ruby    File.read("file.txt")
Python  open("file.txt").read()
  • 12
    Java не так уж плох, если вы знаете правильные инструменты. import org.apache.commons.io.FileUtils; FileUtils.readFileToString (новый файл ("file.txt", "UTF-8")
  • 23
    Этот комментарий пропускает суть языкового дизайна. Таким образом, любой язык, который имеет простую библиотечную функцию для выполнения именно той операции, которую вы хотите выполнить, так же хорош, как и его синтаксис вызова функции. При наличии бесконечной и 100% запомненной библиотеки все программы будут реализованы с помощью одного вызова функции. Язык программирования хорош, когда ему нужно меньше готовых компонентов, чтобы достичь определенного результата.
Теги:

17 ответов

400
Лучший ответ
val lines = scala.io.Source.fromFile("file.txt").mkString
Кстати, "scala." на самом деле не обязательно, как и всегда в области видимости, и вы можете, конечно, импортировать содержимое, полностью или частично, и избегать добавления "io". тоже.

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

val source = scala.io.Source.fromFile("file.txt")
val lines = try source.mkString finally source.close()

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

source.getLines mkString "\n"
  • 0
    Ах, определенно лучше, чем lowerLeft ( + ).
  • 43
    Я опоздал на вечеринку, но я бы не хотел, чтобы люди не знали, что они могут сделать "io.File (" / etc / passwd "). Slurp" в транке.
Показать ещё 23 комментария
54

Чтобы расширить решение Daniel, вы можете значительно сократить его, вставив следующий импорт в любой файл, который требует манипуляции с файлами:

import scala.io.Source._

С этим вы можете теперь:

val lines = fromFile("file.txt").getLines

Я бы с осторожностью прочитал весь файл в одном String. Это очень плохая привычка, которая укусит вас раньше и сложнее, чем вы думаете. Метод getLines возвращает значение типа Iterator[String]. Это эффективный ленивый курсор в файл, позволяющий вам просматривать только нужные вам данные, не рискуя переутомлением памяти.

О, и ответить на ваш предполагаемый вопрос о Source: да, это каноническая библиотека ввода-вывода. В большинстве случаев код заканчивается использованием java.io из-за его интерфейса более низкого уровня и лучшей совместимости с существующими фреймворками, но любой код, который имеет выбор, должен использовать Source, особенно для простой манипуляции файлами.

  • 0
    ХОРОШО. Есть история для моего негативного впечатления от Source: я когда-то был в другой ситуации, чем сейчас, где у меня был очень большой файл, который не помещался в память. Использование Source вызвало сбой программы; оказалось, что он пытался прочитать все это сразу.
  • 7
    Источник не должен читать весь файл в память. Если вы используете toList после getLines или какой-либо другой метод, который создаст коллекцию, то вы получите все в память. Теперь Source - это хак , предназначенный для выполнения работы, а не тщательно продуманная библиотека. Он будет улучшен в Scala 2.8, но у сообщества Scala есть определенная возможность принять активное участие в определении хорошего API ввода-вывода.
35
// for file with utf-8 encoding
val lines = scala.io.Source.fromFile("file.txt", "utf-8").getLines.mkString
  • 6
    Добавление getLines к исходному ответу удалит все новые строки. Должно быть "Source.fromFile (" file.txt "," utf-8 "). MkString".
  • 9
    См. Также мой комментарий в ответе Даниэля С. Собрала - это использование не закроет экземпляр Source, поэтому Scala может сохранить блокировку файла.
26

(EDIT: Это не работает в scala 2.9 и, возможно, не 2.8)

Использовать соединительную линию:

scala> io.File("/etc/passwd").slurp
res0: String = 
##
# User Database
# 
... etc
  • 14
    " slurp "? Действительно ли мы отказались от очевидного, интуитивного названия? Проблема с slurp том, что после этого он может иметь смысл для кого-то с английским языком, по крайней мере, но вы никогда бы не подумали об этом с самого начала!
  • 5
    Просто наткнулся на этот вопрос / ответ. File больше не в 2.8.0, не так ли?
Показать ещё 5 комментариев
15
import java.nio.charset.StandardCharsets._
import java.nio.file.{Files, Paths}

new String(Files.readAllBytes(Paths.get("file.txt")), UTF_8)

Управление кодировкой символов и отсутствие ресурсов для очистки. Также возможно оптимизировано (например, Files.readAllBytes распределяющее массив байтов, соответствующий размеру файла).

7

Мне сказали, что Source.fromFile проблематичен. Лично у меня возникли проблемы с открытием больших файлов с помощью Source.fromFile и пришлось прибегать к Java InputStreams.

Еще одно интересное решение - использование scalax. Вот пример некоторого хорошо прокомментированного кода, который открывает файл журнала с помощью ManagedResource для открытия файла с помощью помощников scalax: http://pastie.org/pastes/420714

  • 1
    +1 за упоминание скалакса
6

Использование getLines() в scala.io.Source отбрасывает, какие символы использовались для терминаторов строк (\n,\r,\r\n и т.д.)

Следующее должно сохранить символ character-for-character и не выполнять чрезмерную конкатенацию строк (проблемы с производительностью):

def fileToString(file: File, encoding: String) = {
  val inStream = new FileInputStream(file)
  val outStream = new ByteArrayOutputStream
  try {
    var reading = true
    while ( reading ) {
      inStream.read() match {
        case -1 => reading = false
        case c => outStream.write(c)
      }
    }
    outStream.flush()
  }
  finally {
    inStream.close()
  }
  new String(outStream.toByteArray(), encoding)
}
5

Как и в Java, с помощью библиотеки CommonsIO:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

Кроме того, многие ответы здесь забывают о Charset. Лучше всегда предоставлять его явно, или он ударит один день.

4

Еще одно: https://github.com/pathikrit/better-files#streams-and-codecs

Различные способы разметки файла без загрузки содержимого в память:

val bytes  : Iterator[Byte]            = file.bytes
val chars  : Iterator[Char]            = file.chars
val lines  : Iterator[String]          = file.lines
val source : scala.io.BufferedSource   = file.content 

Вы можете предоставить свой собственный кодек для всего, что делает чтение/запись (он предполагает scala.io.Codec.default, если вы его не предоставляете):

val content: String = file.contentAsString  // default codec
// custom codec:
import scala.io.Codec
file.contentAsString(Codec.ISO8859)
//or
import scala.io.Codec.string2codec
file.write("hello world")(codec = "US-ASCII")
4

Для эмуляции синтаксиса Ruby (и передачи семантики) открытия и чтения файла рассмотрите этот неявный класс (Scala 2.10 и верхний),

import java.io.File

def open(filename: String) = new File(filename)

implicit class RichFile(val file: File) extends AnyVal {
  def read = io.Source.fromFile(file).getLines.mkString("\n")
}

Таким образом,

open("file.txt").read
3

Для более быстрого общего чтения/загрузки (большого) файла рассмотрите возможность увеличения размера bufferSize (Source.DefaultBufSize на 2048), например, следующим образом:

val file = new java.io.File("myFilename")
io.Source.fromFile(file, bufferSize = Source.DefaultBufSize * 2)

Примечание Source.scala. Для дальнейшего обсуждения см. Scala быстрый текстовый файл для чтения и загрузки в память.

3

вы также можете использовать Path из scala io для чтения и обработки файлов.

import scalax.file.Path

Теперь вы можете получить путь к файлу, используя это: -

val filePath = Path("path_of_file_to_b_read", '/')
val lines = file.lines(includeTerminator = true)

Вы также можете включить терминаторы, но по умолчанию установлено значение false.

3

поскольку несколько человек упомянули scala.io.Source лучше избегать из-за утечек соединения.

Вероятно, scalax и чистые java-библиотеки, такие как commons-io, являются лучшими опциями до тех пор, пока новый проект инкубатора (т.е. scala -io) не будет слит.

2

Очевидный вопрос: "Почему вы хотите читать во всем файле?" Это, очевидно, не масштабируемое решение, если ваши файлы становятся очень большими. scala.io.Source возвращает Iterator[String] метод getLines, который очень полезен и краток.

Не так много работы, чтобы придумать неявное преобразование, используя базовые утилиты java IO для преобразования File, a Reader или InputStream в String. Я считаю, что отсутствие масштабируемости означает, что они не соответствуют этому стандарту API.

  • 11
    Шутки в сторону? Сколько файлов вы действительно читаете на регулярной основе, которые имеют реальные проблемы в памяти? Подавляющее большинство файлов в подавляющем большинстве программ, с которыми я когда-либо имел дело, легко достаточно малы, чтобы поместиться в память. Честно говоря, большие файлы данных являются исключением, и вы должны понимать это и программировать соответствующим образом, если вы собираетесь их читать / записывать.
  • 8
    oxbow_lakes, я не согласен. Существует много ситуаций, связанных с небольшими файлами, размер которых не будет увеличиваться в будущем.
Показать ещё 1 комментарий
1
import scala.io.source
object ReadLine{
def main(args:Array[String]){
if (args.length>0){
for (line <- Source.fromLine(args(0)).getLine())
println(line)
}
}

в аргументах вы можете указать путь к файлу, и он вернет все строки

  • 1
    Что это предлагает, чего нет в другом ответе?
  • 0
    Других ответов не видел ... просто думал, что могу внести свой вклад вот так размещено ... надеюсь, что это никому не повредит :)
Показать ещё 1 комментарий
1

Вам не нужно анализировать каждую строку, а затем снова конкатенировать их...

Source.fromFile(path)(Codec.UTF8).mkString

Я предпочитаю использовать это:

import scala.io.{BufferedSource, Codec, Source}
import scala.util.Try

def readFileUtf8(path: String): Try[String] = Try {
  val source: BufferedSource = Source.fromFile(path)(Codec.UTF8)
  val content = source.mkString
  source.close()
  content
}
  • 0
    Вы должны закрыть поток - если ошибка возникает в val content = source.mkString
1

напечатайте каждую строку, например, используйте Java BufferedReader для чтения строки ervery и напечатайте ее:

scala.io.Source.fromFile("test.txt" ).foreach{  print  }

эквивалентны:

scala.io.Source.fromFile("test.txt" ).foreach( x => print(x))

Ещё вопросы

Сообщество Overcoder
Наверх
Меню