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

312

Какой простой и канонический способ чтения всего файла в память в 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")
smartnut007
25
Этот комментарий пропускает смысл языкового дизайна. Таким образом, любой язык, который имеет простую библиотечную функцию для выполнения именно той операции, которую вы хотите выполнить, так же хорош, как и его синтаксис вызова функции. При наличии бесконечной и 100% запомненной библиотеки все программы будут реализованы с помощью одного вызова функции. Язык программирования хорош, когда для его достижения требуется меньше готовых компонентов.
Крис Маунтфорд

Ответы:

430
val lines = scala.io.Source.fromFile("file.txt").mkString

Кстати, "scala. » на самом деле не является необходимым, так как в любом случае он всегда находится в области видимости, и вы, конечно, можете импортировать содержимое io полностью или частично, и избежать необходимости добавлять «io». слишком.

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

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

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

source.getLines mkString "\n"
Даниэль С. Собрал
источник
48
Я опоздал на вечеринку, но я бы не хотел, чтобы люди не знали, что они могут сделать "io.File (" / etc / passwd "). Slurp" в транке.
PSP
28
@extempore Если вы действительно думаете, что я неблагодарный, мне очень жаль. Я глубоко ценю вашу поддержку языка Scala и каждый раз, когда вы лично рассматривали проблему, которую я поднял, предлагали решение проблемы, которую я имел, или объясняли мне что-то. Я воспользуюсь случаем, чтобы поблагодарить вас за то, что вы превратили scala.io во что-то достойное и достойное. Теперь я буду больше выражать благодарность, но я все еще ненавижу это имя, извините.
Даниэль С. Собрал,
49
«slurp» - это название для чтения всего файла в Perl в течение многих лет. Perl имеет более интуитивную и неформальную традицию именования, чем семейство языков C, что некоторым может показаться неприятным, но в этом случае я думаю, что оно подходит: это уродливое слово для уродливой практики. Когда вы хлебаете (), вы знаете, что делаете что-то непослушное, потому что вам просто нужно было это напечатать.
Маркус Даунинг
15
File.read () будет более подходящим именем, и, кроме того, будет соответствовать Ruby и Python.
Брендан Оконнор
26
@extempore: вы не можете остановить людей от отвращения. Просто так оно и есть. Вас не должно беспокоить то, что некоторым людям не нравится каждый ваш выбор. Это просто жизнь, вы не можете угодить всем :)
Алекс Бараноски,
58

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

import scala.io.Source._

С этим теперь вы можете сделать:

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

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

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

Даниэль Спивак
источник
ХОРОШО. Есть история для моего негативного впечатления от Source: я когда-то был в другой ситуации, чем сейчас, где у меня был очень большой файл, который не помещался в память. Использование Source вызвало сбой программы; оказалось, что он пытался прочитать все это сразу.
Брендан Оконнор
7
Источник не должен читать весь файл в память. Если вы используете toList после getLines или какой-либо другой метод, который создаст коллекцию, то вы получите все в память. Теперь Source - это хак , предназначенный для выполнения работы, а не тщательно продуманная библиотека. Он будет улучшен в Scala 2.8, но у сообщества Scala есть определенная возможность принять активное участие в определении хорошего API ввода-вывода.
Даниэль С. Собрал
36
// 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".
Joe23
9
Смотрите также мой комментарий в ответе Даниэля С. Собрала - это использование не закроет экземпляр Source, поэтому Scala может сохранить блокировку файла.
DJJ
26

(РЕДАКТИРОВАТЬ: Это не работает в Scala 2.9 и, возможно, не 2.8)

Используйте ствол:

scala> io.File("/etc/passwd").slurp
res0: String = 
##
# User Database
# 
... etc
PSP
источник
14
" slurp"? Действительно ли мы отказались от очевидного, интуитивного названия? Проблема в slurpтом, что это может иметь смысл после того, как кто-то с английским языком по крайней мере, по крайней мере, но вы никогда не подумали бы об этом с самого начала!
Даниэль С. Собрал
5
Просто наткнулся на этот вопрос / ответ. Fileбольше не в 2.8.0, не так ли?
huynhjl
4
хлебать звучит здорово. :) Я бы этого не ожидал, но я не ожидал, что вывод на экран будет называться «печать». slurpЭто фантастично! :) Было фантастически? Я не нахожу это. ; (
пользователь неизвестен
5
в scala-2.10.0 имя пакета - scala.reflect.io.File и вопрос об этом «файле». extempore, почему этот файл помечен как «экспериментальный»? Это безопасно? Это освобождает блокировку файловой системы?
Василий Новиков
4
У slurp есть длинная история для этой цели, я думаю, из Perl
Крис Маунтфорд
18
import java.nio.charset.StandardCharsets._
import java.nio.file.{Files, Paths}

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

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

Пол Дрэйпер
источник
Чтобы упростить, вы можете использовать Files.readString​(Path.of("file.txt"), StandardCharsets.UTF_8).
Ава
7

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

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

Икай Лан
источник
6

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

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

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)
}
Muyyatin
источник
6

Еще один: 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")
pathikrit
источник
5

Как и в Java, используя библиотеку CommonsIO:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

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

Дмитрий Лазерка
источник
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

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

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

поко
источник
3

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

import scalax.file.Path

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

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

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

Atiq
источник
3

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

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

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

вяз
источник
3

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

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
}
комонада
источник
Вы должны закрыть поток - если возникает ошибкаval content = source.mkString
Анджей Йозвик
+1 за Codec. Я получил неудачный тест, sbt testпотому что не могу его установить, в то время как тестовая команда Intellij прошла все тесты. И вы можете использовать def usingот этого
Михаил Ионкин
3

Если вы не возражаете против сторонней зависимости, вам следует рассмотреть возможность использования моей библиотеки OS-Lib . Это делает чтение / запись файлов и работу с файловой системой очень удобной:

// Make sure working directory exists and is empty
val wd = os.pwd/"out"/"splash"
os.remove.all(wd)
os.makeDir.all(wd)

// Read/write files
os.write(wd/"file.txt", "hello")
os.read(wd/"file.txt") ==> "hello"

// Perform filesystem operations
os.copy(wd/"file.txt", wd/"copied.txt")
os.list(wd) ==> Seq(wd/"copied.txt", wd/"file.txt")

с однострочными помощниками для чтения байтов , чтения фрагментов , чтения строк и многих других полезных / общих операций

Ли Хаои
источник
2

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

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

oxbow_lakes
источник
12
Шутки в сторону? Сколько файлов вы действительно читаете на регулярной основе, которые имеют реальные проблемы в памяти? Подавляющее большинство файлов в подавляющем большинстве программ, с которыми я когда-либо имел дело, достаточно малы, чтобы поместиться в память. Честно говоря, большие файлы данных являются исключением, и вы должны понимать это и программировать соответственно, если вы собираетесь их читать / записывать.
Кристофер
8
oxbow_lakes, я не согласен. Существует много ситуаций, связанных с небольшими файлами, размер которых не будет увеличиваться в будущем.
Брендан Оконнор
4
Я согласен с тем, что они являются исключением - но я думаю, именно поэтому чтение всего файла в память отсутствует ни в JDK, ни в Scala SDK. Это 3-х строчный метод для вас, чтобы написать самому: преодолеть его
oxbow_lakes
1

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

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

эквивалент:

scala.io.Source.fromFile("test.txt" ).foreach( x => print(x))
gordonpro
источник
0
import scala.io.source
object ReadLine{
def main(args:Array[String]){
if (args.length>0){
for (line <- Source.fromLine(args(0)).getLine())
println(line)
}
}

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

Apurw
источник
3
Что это предлагает, чего нет в другом ответе?
17
Других ответов не видел ... просто думал, что могу внести свой вклад вот так размещено ... надеюсь, это никому не повредит :)
Apurw
1
Вы действительно должны прочитать их. Большинство из них довольно информативны. Даже те, кому 8 лет, имеют соответствующую информацию.
17