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

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

Ответ 1

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

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


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

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

Зонтик-проект Scala IO состоит из нескольких подпроектов для различных аспектов и расширений 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)
  }

Ответ 2

Это одна из функций, отсутствующих в стандартном 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)
}

Ответ 3

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

/**
 * 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)
    }
  }

и др.

Ответ 4

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

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()
  }

Ответ 5

Дайте другой ответ, потому что мои правки других ответов отклоняются.

Это самый сжатый и простой ответ (похожий на Garret Hall's)

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 или lambdas, а также использование синтаксиса placeholder. Также обратите внимание на лучшее именование.

Ответ 6

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

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

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

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}

Ответ 7

Один вкладыш для сохранения/чтения в/из 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

Ответ 9

ОБНОВЛЕНИЕ 2019/сентябрь/01:

  • Начиная с Scala 2.13, предпочитайте использовать scala.util.Using
  • Исправлена ошибка, из-за которой finally глотал оригинальный Exception, брошенный try, если код finally бросил Exception

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

  1. В ответе Jus12 использование каррирования для использования вспомогательного метода не очевидно для начинающих Scala/FP
  2. Необходимо инкапсулировать ошибки более низкого уровня с помощью scala.util.Try
  3. Необходимо показать Java-разработчикам, плохо знакомым с Scala/FP, как правильно вкладывать зависимые ресурсы, чтобы метод 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 => {
        var optionExceptionTry: Option[Exception] = None
        try
          transfer(autoCloseable)
        catch {
          case exceptionTry: Exception =>
            optionExceptionTry = Some(exceptionTry)
            throw exceptionTry
        }
        finally
          try
            autoCloseable.close()
          catch {
            case exceptionFinally: Exception =>
              optionExceptionTry match {
                case Some(exceptionTry) =>
                  exceptionTry.addSuppressed(exceptionFinally)
                case None =>
                  throw exceptionFinally
              }
          }
      }
    )

Начало описанного выше метода 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 гарантирует, что метод finalize будет вызван GC для 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 => {
        var optionExceptionTry: Option[Exception] = None
        try
          transfer(source)
        catch {
          case exceptionTry: Exception =>
            optionExceptionTry = Some(exceptionTry)
            throw exceptionTry
        }
        finally
          try
            source.close()
          catch {
            case exceptionFinally: Exception =>
              optionExceptionTry match {
                case Some(exceptionTry) =>
                  exceptionTry.addSuppressed(exceptionFinally)
                case None =>
                  throw exceptionFinally
              }
          }
      }
    )

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

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
      )
  )

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


Теперь, собрав все это вместе с извлеченным импортом (значительно упростив вставку в рабочую таблицу 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) //parameter list 1
  (transfer: A => scala.util.Try[R])  //parameter list 2
: scala.util.Try[R] =
  Try(instantiateAutoCloseable())
    .flatMap(
      autoCloseable => {
        var optionExceptionTry: Option[Exception] = None
        try
          transfer(autoCloseable)
        catch {
          case exceptionTry: Exception =>
            optionExceptionTry = Some(exceptionTry)
            throw exceptionTry
        }
        finally
          try
            autoCloseable.close()
          catch {
            case exceptionFinally: Exception =>
              optionExceptionTry match {
                case Some(exceptionTry) =>
                  exceptionTry.addSuppressed(exceptionFinally)
                case None =>
                  throw exceptionFinally
              }
          }
      }
    )

def tryUsingSource[S <: scala.io.Source, R]
  (instantiateSource: () => S)
  (transfer: S => scala.util.Try[R])
: scala.util.Try[R] =
  Try(instantiateSource())
    .flatMap(
      source => {
        var optionExceptionTry: Option[Exception] = None
        try
          transfer(source)
        catch {
          case exceptionTry: Exception =>
            optionExceptionTry = Some(exceptionTry)
            throw exceptionTry
        }
        finally
          try
            source.close()
          catch {
            case exceptionFinally: Exception =>
              optionExceptionTry match {
                case Some(exceptionTry) =>
                  exceptionTry.addSuppressed(exceptionFinally)
                case None =>
                  throw exceptionFinally
              }
          }
      }
    )

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 быстрее справиться с этой проблемой.

Ответ 10

К сожалению для лучшего ответа, 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")

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

Ответ 11

Здесь приведен пример записи некоторых строк в файл с помощью scalaz-stream.

import scalaz._
import scalaz.stream._

def writeLinesToFile(lines: Seq[String], file: String): Task[Unit] =
  Process(lines: _*)              // Process that enumerates the lines
    .flatMap(Process(_, "\n"))    // Add a newline after each line
    .pipe(text.utf8Encode)        // Encode as UTF-8
    .to(io.fileChunkW(fileName))  // Buffered write to the file
    .runLog[Task, Unit]           // Get this computation as a Task
    .map(_ => ())                 // Discard the result

writeLinesToFile(Seq("one", "two"), "file.txt").run

Ответ 12

Начиная с Scala 2.13, стандартная библиотека предоставляет специальную утилиту управления ресурсами: Using.

В этом случае его можно использовать с такими ресурсами, как PrintWriter или BufferedWriter который расширяет AutoCloseable для записи в файл и, несмотря ни на что, впоследствии закрывает ресурс:

  • Например, с помощью java.io api:

    import scala.util.Using, java.io.{PrintWriter, File}
    
    // val lines = List("hello", "world")
    Using(new PrintWriter(new File("file.txt"))) {
      writer => lines.foreach(writer.println)
    }
    
  • Или с помощью java.nio api:

    import scala.util.Using, java.nio.file.{Files, Paths}, java.nio.charset.Charset
    
    // val lines = List("hello", "world")
    Using(Files.newBufferedWriter(Paths.get("file.txt"), Charset.forName("UTF-8"))) {
      writer => lines.foreach(line => writer.write(line + "\n"))
    }
    

Ответ 13

Чтобы превзойти samthebest и вкладчиков перед ним, я улучшил наименование и краткость:

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

  def writeStringToFile(file: File, data: String, appending: Boolean = false) =
    using(new FileWriter(file, appending))(_.write(data))

Ответ 14

Без зависимостей, с обработкой ошибок

  • Использует только методы из стандартной библиотеки
  • Создает каталоги для файла, если необходимо
  • Использует Either для обработки ошибок

код

def write(destinationFile: Path, fileContent: String): Either[Exception, Path] =
  write(destinationFile, fileContent.getBytes(StandardCharsets.UTF_8))

def write(destinationFile: Path, fileContent: Array[Byte]): Either[Exception, Path] =
  try {
    Files.createDirectories(destinationFile.getParent)
    // Return the path to the destinationFile if the write is successful
    Right(Files.write(destinationFile, fileContent))
  } catch {
    case exception: Exception => Left(exception)
  }

Использование

val filePath = Paths.get("./testDir/file.txt")

write(filePath , "A test") match {
  case Right(pathToWrittenFile) => println(s"Successfully wrote to $pathToWrittenFile")
  case Left(exception) => println(s"Could not write to $filePath. Exception: $exception")
}

Ответ 15

Если вы в любом случае имеете Akka Streams в своем проекте, он предоставляет однострочный:

def writeToFile(p: Path, s: String)(implicit mat: Materializer): Unit = {
  Source.single(ByteString(s)).runWith(FileIO.toPath(p))
}

Akka docs> Streaming File IO

Ответ 16

2019 Обновление:

Резюме - Java NIO (или NIO.2 для async) по-прежнему является наиболее полным решением для обработки файлов, поддерживаемым в Scala. Следующий код создает и записывает некоторый текст в новый файл:

import java.io.{BufferedOutputStream, OutputStream}
import java.nio.file.{Files, Paths}

val testFile1 = Paths.get("yourNewFile.txt")
val s1 = "text to insert in file".getBytes()

val out1: OutputStream = new BufferedOutputStream(
  Files.newOutputStream(testFile1))

try {
  out1.write(s1, 0, s1.length)
} catch {
  case _ => println("Exception thrown during file writing")
} finally {
  out1.close()
}
  1. Импорт библиотек Java: IO и NIO
  2. Создайте объект Path с выбранным вами именем файла
  3. Преобразуйте ваш текст, который вы хотите вставить в файл, в байтовый массив
  4. Получите ваш файл в виде потока: OutputStream
  5. Передайте ваш байтовый массив в вашу функцию write потока вывода
  6. Закрыть поток

Ответ 17

Аналогично этому ответу, вот пример с fs2 (версия 1.0.4):

import cats.effect._

import fs2._
import fs2.io

import java.nio.file._

import scala.concurrent.ExecutionContext
import scala.language.higherKinds
import cats.syntax.functor._

object ScalaApp extends IOApp {

  def write[T[_]](p: Path, s: String)
                 (implicit F: ConcurrentEffect[T], cs: ContextShift[T]): T[Unit] = {
    Stream(s)
      .covary[T]
      .through(text.utf8Encode)
      .through(
        io.file.writeAll(
          p,
          scala.concurrent.ExecutionContext.global,
          Seq(StandardOpenOption.CREATE)
        )
      )
      .compile
      .drain
  }


  def run(args: List[String]): IO[ExitCode] = {

    implicit val executionContext: ExecutionContext =
      scala.concurrent.ExecutionContext.Implicits.global

    implicit val contextShift: ContextShift[IO] =
      IO.contextShift(executionContext)

    val outputFile: Path = Paths.get("output.txt")

    write[IO](outputFile, "Hello world\n").as(ExitCode.Success)

  }
}

Ответ 18

Эта строка помогает записать файл из массива или строки.

 new PrintWriter(outputPath) { write(ArrayName.mkString("")); close }