Как записать в файл в Scala? - PullRequest
       63

Как записать в файл в Scala?

152 голосов
/ 05 января 2011

Для чтения есть полезная абстракция Source. Как я могу записать строки в текстовый файл?

Ответы [ 18 ]

209 голосов
/ 05 января 2011

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

def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
  val p = new java.io.PrintWriter(f)
  try { op(p) } finally { p.close() }
}

и он используется так:

import java.io._
val data = Array("Five","strings","in","a","file!")
printToFile(new File("example.txt")) { p =>
  data.foreach(p.println)
}
69 голосов
/ 05 января 2011

Редактировать 2019 (8 лет спустя), Scala-IO не очень активен, если есть, Ли Хаои предлагает свою собственную библиотеку lihaoyi/os-lib,что он представляет ниже .

Июнь 2019 г., Ксавье Гихот упоминает в свой ответ в библиотеке Using, утилита для автоматического управления ресурсами.


Редактировать (сентябрь 2011 г.): с Эдуардо Коста запрашивает Scala2.9, а с Рик-777 комментирует, что scalax.IO история коммитов практически отсутствует с середины 2009 года ...

Scala-IO изменил место: см. Его GitHub репо , с Джесси Эйхар (также на SO ):

Scala IOЗонт-проект состоит из нескольких подпроектов для различных аспектов и расширений ввода-вывода.
Существует два основных компонента Scala IO:

  • Core - Core в основном занимается чтениеми приказпоступление данных в произвольные источники и приемники и из них.Краеугольными камнями являются Input, Output и Seekable, которые обеспечивают базовый API.
    Другие классы важности: Resource, ReadChars и WriteChars.
  • Файл - Файл представляет собой API File (называемый Path), который основан на комбинации файловой системы Java 7 NIO и API-интерфейсов SBT PathFinder.
    Path и FileSystem являются основными точками входав Scala IO File API.
import scalax.io._

val output:Output = Resource.fromFile("someFile")

// Note: each write will open a new connection to file and 
//       each write is executed at the begining of the file,
//       so in this case the last write will be the contents of the file.
// See Seekable for append and patching files
// Also See openOutput for performing several writes with a single connection

output.writeIntsAsBytes(1,2,3)
output.write("hello")(Codec.UTF8)
output.writeStrings(List("hello","world")," ")(Codec.UTF8)

Оригинальный ответ (январь 2011 г.) со старым местом для scala-io:

Если вы нене хотите ждать Scala2.9, вы можете использовать библиотеку scala-инкубатор / scala-io .
(как упомянуто в "" Почему источник Scala не закрывает базовый InputStream?")

См. образцы

{ // several examples of writing data
    import scalax.io.{
      FileOps, Path, Codec, OpenOption}
    // the codec must be defined either as a parameter of ops methods or as an implicit
    implicit val codec = scalax.io.Codec.UTF8


    val file: FileOps = Path ("file")

    // write bytes
    // By default the file write will replace
    // an existing file with the new data
    file.write (Array (1,2,3) map ( _.toByte))

    // another option for write is openOptions which allows the caller
    // to specify in detail how the write should take place
    // the openOptions parameter takes a collections of OpenOptions objects
    // which are filesystem specific in general but the standard options
    // are defined in the OpenOption object
    // in addition to the definition common collections are also defined
    // WriteAppend for example is a List(Create, Append, Write)
    file.write (List (1,2,3) map (_.toByte))

    // write a string to the file
    file.write("Hello my dear file")

    // with all options (these are the default options explicitely declared)
    file.write("Hello my dear file")(codec = Codec.UTF8)

    // Convert several strings to the file
    // same options apply as for write
    file.writeStrings( "It costs" :: "one" :: "dollar" :: Nil)

    // Now all options
    file.writeStrings("It costs" :: "one" :: "dollar" :: Nil,
                    separator="||\n||")(codec = Codec.UTF8)
  }
49 голосов
/ 07 марта 2011

Похоже на ответ Рекса Керра, но более общее.Сначала я использую вспомогательную функцию:

/**
 * Used for reading/writing to database, files, etc.
 * Code From the book "Beginning Scala"
 * http://www.amazon.com/Beginning-Scala-David-Pollak/dp/1430219890
 */
def using[A <: {def close(): Unit}, B](param: A)(f: A => B): B =
try { f(param) } finally { param.close() }

Затем я использую это как:

def writeToFile(fileName:String, data:String) = 
  using (new FileWriter(fileName)) {
    fileWriter => fileWriter.write(data)
  }

и

def appendToFile(fileName:String, textData:String) =
  using (new FileWriter(fileName, true)){ 
    fileWriter => using (new PrintWriter(fileWriter)) {
      printWriter => printWriter.println(textData)
    }
  }

и т.д.

35 голосов
/ 11 февраля 2013

Простой ответ:

import java.io.File
import java.io.PrintWriter

def writeToFile(p: String, s: String): Unit = {
    val pw = new PrintWriter(new File(p))
    try pw.write(s) finally pw.close()
  }
20 голосов
/ 23 ноября 2013

Дать другой ответ, потому что мои правки других ответов были отклонены.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

File("filename").writeAll("hello world")

Это похоже на Jus12, но без многословия и с правильным стилем кода

def using[A <: {def close(): Unit}, B](resource: A)(f: A => B): B =
  try f(resource) finally resource.close()

def writeToFile(path: String, data: String): Unit = 
  using(new FileWriter(path))(_.write(data))

def appendToFile(path: String, data: String): Unit =
  using(new PrintWriter(new FileWriter(path, true)))(_.println(data))

Обратите внимание, что вам НЕ нужны фигурные скобки для try finally и лямбда-выражения, и обратите внимание на использование синтаксиса заполнителя. Также обратите внимание на лучшее именование.

13 голосов
/ 02 декабря 2013

Один вкладыш для сохранения / чтения в / из String, используя java.nio.

import java.nio.file.{Paths, Files, StandardOpenOption}
import java.nio.charset.{StandardCharsets}
import scala.collection.JavaConverters._

def write(filePath:String, contents:String) = {
  Files.write(Paths.get(filePath), contents.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE)
}

def read(filePath:String):String = {
  Files.readAllLines(Paths.get(filePath), StandardCharsets.UTF_8).asScala.mkString
}

Это не подходит для больших файлов, но сделает работу.

Некоторые ссылки:

java.nio.file.Files.write
java.lang.String.getBytes
scala.collection.JavaConverters
scala.collection.immutable.List.mkString

13 голосов
/ 13 сентября 2013

Вот краткий однострочный текст с использованием библиотеки компилятора Scala:

scala.tools.nsc.io.File("filename").writeAll("hello world")

В качестве альтернативы, если вы хотите использовать библиотеки Java, вы можете сделать это взломать:

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}
7 голосов
/ 14 сентября 2015

Микро библиотека, которую я написал: https://github.com/pathikrit/better-files

file.appendLine("Hello", "World")

или

file << "Hello" << "\n" << "World"
5 голосов
/ 15 декабря 2015

Изучив все эти ответы о том, как легко написать файл в Scala, и некоторые из них довольно хороши, у меня возникло три вопроса:

  1. В ответе Jus12 использование каррирования для использования вспомогательного метода не очевидно для начинающих Scala / FP
  2. Необходимо инкапсулировать ошибки более низкого уровня с scala.util.Try
  3. Необходимо показать новичкам в Scala / FP разработчиков Java, как правильно вкладывать зависимые ресурсы, поэтому метод close выполняется для каждого зависимого ресурса в обратном порядке - Примечание: закрытие зависимые ресурсы в обратном порядке ОСОБЕННО В СЛУЧАЕ ОТКАЗА является редко понимаемым требованием спецификации java.lang.AutoCloseable, которая имеет тенденцию приводить к очень губительным и трудным для поиска ошибок и времени выполнения. неудачи

Прежде чем начать, моя цель - не лаконичность. Это облегчает понимание новичкам в Scala / FP, обычно начинающим с Java. В самом конце я соберу все биты вместе, а затем увеличу краткость.

Во-первых, необходимо обновить метод using, чтобы использовать Try (опять же, краткость здесь не является целью). Он будет переименован в tryUsingAutoCloseable:

def tryUsingAutoCloseable[A <: AutoCloseable, R]
  (instantiateAutoCloseable: () => A) //parameter list 1
  (transfer: A => scala.util.Try[R])  //parameter list 2
: scala.util.Try[R] =
  Try(instantiateAutoCloseable())
    .flatMap(
      autoCloseable =>
        try
          transfer(autoCloseable)
        finally
          autoCloseable.close()
    )

Начало описанного выше метода tryUsingAutoCloseable может сбивать с толку, поскольку он, кажется, имеет два списка параметров вместо обычного списка с одним параметром. Это называется карри. И я не буду вдаваться в подробности, как работает карри или где это иногда полезно. Оказывается, для этой конкретной проблемной области это правильный инструмент для работы.

Далее нам нужно создать метод tryPrintToFile, который создаст (или перезапишет существующий) File и напишет List[String]. Он использует FileWriter, который инкапсулирован в BufferedWriter, который в свою очередь инкапсулирован в PrintWriter. А для повышения производительности определяется размер буфера по умолчанию, намного превышающий размер по умолчанию для BufferedWriter, defaultBufferSize и ему присваивается значение 65536.

Вот код (и опять же, краткость здесь не является целью):

val defaultBufferSize: Int = 65536

def tryPrintToFile(
  lines: List[String],
  location: java.io.File,
  bufferSize: Int = defaultBufferSize
): scala.util.Try[Unit] = {
  tryUsingAutoCloseable(() => new java.io.FileWriter(location)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
    fileWriter =>
      tryUsingAutoCloseable(() => new java.io.BufferedWriter(fileWriter, bufferSize)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
        bufferedWriter =>
          tryUsingAutoCloseable(() => new java.io.PrintWriter(bufferedWriter)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
            printWriter =>
              scala.util.Try(
                lines.foreach(line => printWriter.println(line))
              )
          }
      }
  }
}

Приведенный выше метод tryPrintToFile полезен тем, что он принимает List[String] в качестве входных данных и отправляет его File. Давайте теперь создадим метод tryWriteToFile, который берет String и записывает его в File.

Вот код (и я позволю вам угадать приоритет краткости здесь):

def tryWriteToFile(
  content: String,
  location: java.io.File,
  bufferSize: Int = defaultBufferSize
): scala.util.Try[Unit] = {
  tryUsingAutoCloseable(() => new java.io.FileWriter(location)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
    fileWriter =>
      tryUsingAutoCloseable(() => new java.io.BufferedWriter(fileWriter, bufferSize)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
        bufferedWriter =>
          Try(bufferedWriter.write(content))
      }
  }
}

Наконец, полезно иметь возможность извлекать содержимое File как String. В то время как scala.io.Source предоставляет удобный метод для простого получения содержимого File, метод close должен использоваться в Source для освобождения базовой JVM и дескрипторов файловой системы. Если этого не сделать, то ресурс не будет освобожден, пока JCM GC (Сборщик мусора) не сможет освободить сам экземпляр Source. И даже в этом случае слабая JVM гарантирует, что GC будет вызывать метод finalize для ресурса close. Это означает, что клиент несет ответственность за явный вызов метода close, точно так же, как это обязанность клиента - получить close в экземпляре java.lang.AutoCloseable. Для этого нам понадобится второе определение метода using, который обрабатывает scala.io.Source.

Вот код для этого (все еще не является кратким):

def tryUsingSource[S <: scala.io.Source, R]
  (instantiateSource: () => S)
  (transfer: S => scala.util.Try[R])
: scala.util.Try[R] =
  Try(instantiateSource())
    .flatMap(
      source =>
        try
          transfer(source))
        finally
          source.close()
    )

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

def tryProcessSource(
    file: java.io.File
  , parseLine: (String, Int) => List[String] = (line, index) => List(line)
  , filterLine: (List[String], Int) => Boolean = (values, index) => true
  , retainValues: (List[String], Int) => List[String] = (values, index) => values
  , isFirstLineNotHeader: Boolean = false
): scala.util.Try[List[List[String]]] =
  tryUsingSource(scala.io.Source.fromFile(file)) {
    source =>
      scala.util.Try(
        ( for {
            (line, index) <-
              source.getLines().buffered.zipWithIndex
            values =
              parseLine(line, index)
            if (index == 0 && !isFirstLineNotHeader) || filterLine(values, index)
            retainedValues =
              retainValues(values, index)
          } yield retainedValues
        ).toList //must explicitly use toList due to the source.close which will
                 //occur immediately following execution of this anonymous function
      )
  )

обновленная версия вышеуказанной функции была предоставлена ​​в качестве ответа на другой, но связанный вопрос StackOverflow .


Теперь, собрав все это вместе с извлеченным импортом (значительно упростив вставку в рабочую таблицу Scala, присутствующую как в плагине Eclipse ScalaIDE, так и в IntelliJ Scala, упростить вывод данных на рабочий стол, чтобы их было легче изучить с помощью текста. редактор), вот как выглядит код (с повышенной краткостью):

import scala.io.Source
import scala.util.Try
import java.io.{BufferedWriter, FileWriter, File, PrintWriter}

val defaultBufferSize: Int = 65536

def tryUsingAutoCloseable[A <: AutoCloseable, R]
  (instantiateAutoCloseable: () => A)(transfer: A => scala.util.Try[R]): scala.util.Try[R] =
  Try(instantiateAutoCloseable())
    .flatMap(
      autoCloseable =>
        try transfer(autoCloseable)) finally autoCloseable.close()
    )

def tryUsingSource[S <: scala.io.Source, R]
  (instantiateSource: () => S)(transfer: S => scala.util.Try[R]): scala.util.Try[R] =
  Try(instantiateSource())
    .flatMap(
      source =>
        try transfer(source)) finally source.close()
    )

def tryPrintToFile(
  lines: List[String],
  location: File,
  bufferSize: Int = defaultBufferSize
): Try[Unit] =
  tryUsingAutoCloseable(() => new FileWriter(location)) { fileWriter =>
    tryUsingAutoCloseable(() => new BufferedWriter(fileWriter, bufferSize)) { bufferedWriter =>
      tryUsingAutoCloseable(() => new PrintWriter(bufferedWriter)) { printWriter =>
          Try(lines.foreach(line => printWriter.println(line)))
      }
    }
  }

def tryWriteToFile(
  content: String,
  location: File,
  bufferSize: Int = defaultBufferSize
): Try[Unit] =
  tryUsingAutoCloseable(() => new FileWriter(location)) { fileWriter =>
    tryUsingAutoCloseable(() => new BufferedWriter(fileWriter, bufferSize)) { bufferedWriter =>
      Try(bufferedWriter.write(content))
    }
  }

def tryProcessSource(
    file: File,
  parseLine: (String, Int) => List[String] = (line, index) => List(line),
  filterLine: (List[String], Int) => Boolean = (values, index) => true,
  retainValues: (List[String], Int) => List[String] = (values, index) => values,
  isFirstLineNotHeader: Boolean = false
): Try[List[List[String]]] =
  tryUsingSource(Source.fromFile(file)) { source =>
    Try(
      ( for {
          (line, index) <- source.getLines().buffered.zipWithIndex
          values = parseLine(line, index)
          if (index == 0 && !isFirstLineNotHeader) || filterLine(values, index)
          retainedValues = retainValues(values, index)
        } yield retainedValues
      ).toList
    )
  )

Будучи новичком в Scala / FP, я потратил много часов (в основном на расчесывание головы), зарабатывая вышеупомянутые знания и решения.Надеюсь, это поможет другим новичкам в Scala / FP быстрее справиться с этой проблемой.

5 голосов
/ 26 мая 2019

К сожалению, для лучшего ответа, Scala-IO мертв.Если вы не возражаете против использования сторонних зависимостей, рассмотрите возможность использования моей библиотеки 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")

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

...