tipos - ¿Cómo escribir en un archivo en Scala?
scala tutorial (14)
Para leer, está la Source
abstracción útil. ¿Cómo puedo escribir líneas en un archivo de texto?
Sin dependencias, con manejo de errores
- Utiliza métodos de la biblioteca estándar exclusivamente
- Crea directorios para el archivo, si es necesario
- Utiliza
Either
para el manejo de errores
Código
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)
}
Uso
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")
}
Aquí hay un ejemplo de scalaz-stream escribir algunas líneas en un archivo usando 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
Aquí hay un resumen conciso utilizando la biblioteca del compilador de Scala:
scala.tools.nsc.io.File("filename").writeAll("hello world")
Alternativamente, si quiere usar las bibliotecas de Java puede hacer esto:
Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}
Desde una cadena de escritura de scala a un archivo en una declaración
Dando otra respuesta, porque mis ediciones de otras respuestas fueron rechazadas.
Esta es la respuesta más concisa y simple (similar a la de Garret Hall)
File("filename").writeAll("hello world")
Esto es similar a Jus12, pero sin la verborrea y con el estilo de código correcto
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))
Tenga en cuenta que NO necesita las llaves para try finally
, ni lambdas, y tenga en cuenta el uso de la sintaxis placeholder. También tenga en cuenta una mejor denominación.
Después de revisar todas estas respuestas sobre cómo escribir fácilmente un archivo en Scala, y algunas de ellas son bastante buenas, tuve tres problemas:
- En la respuesta del Jus12 , el uso de currying para el método de ayuda de uso no es obvio para los principiantes de Scala / FP
- Necesita encapsular errores de nivel inferior con
scala.util.Try
- Necesita mostrar a los desarrolladores de Java nuevos en Scala / FP cómo jerarquizar adecuadamente los recursos dependientes para que el método de
close
se realice en cada recurso dependiente en orden inverso. Nota: cerrar recursos dependientes en orden inverso ESPECIALMENTE EN CASO DE UNA FALTA es un requisito raramente entendido de la especificaciónjava.lang.AutoCloseable
que tiende a provocar errores muy perniciosos y difíciles de encontrar y fallas de tiempo de ejecución
Antes de comenzar, mi objetivo no es la concisión. Es para facilitar la comprensión de los principiantes de Scala / FP, generalmente los que provienen de Java. Al final, juntaré todas las partes y luego aumentaré la concisión.
Primero, el método de using
debe actualizarse para usar Try
(de nuevo, la concisión no es el objetivo aquí). Se renombrará como 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()
)
El comienzo del método tryUsingAutoCloseable
anterior puede ser confuso porque parece tener dos listas de parámetros en lugar de la lista de parámetros únicos habitual. Esto se llama currying. Y no voy a entrar en detalles sobre cómo funciona el currículum o dónde es ocasionalmente útil. Resulta que para este espacio de problema en particular, es la herramienta adecuada para el trabajo.
A continuación, necesitamos crear el método, tryPrintToFile
, que creará un (o sobrescribirá un File
existente) y escribirá una List[String]
. Utiliza un FileWriter
que está encapsulado por un BufferedWriter
que a su vez está encapsulado por un PrintWriter
. Y para elevar el rendimiento, se define un tamaño de buffer predeterminado mucho más grande que el predeterminado para BufferedWriter
, defaultBufferSize
, y se le asigna el valor 65536.
Aquí está el código (y de nuevo, la concisión no es el objetivo aquí):
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))
)
}
}
}
}
El método tryPrintToFile
anterior es útil porque toma una List[String]
como entrada y la envía a un File
. tryWriteToFile
ahora un método tryWriteToFile
que toma una String
y la escribe en un File
.
Aquí está el código (y le dejaré adivinar la prioridad de concisión aquí):
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))
}
}
}
Finalmente, es útil poder obtener los contenidos de un File
como una String
. Si bien scala.io.Source
proporciona un método conveniente para obtener fácilmente el contenido de un File
, el método de close
debe usarse en el Source
para liberar los identificadores JVM subyacentes y el sistema de archivos. Si no se hace esto, el recurso no se libera hasta que JVM GC (Garbage Collector) se libera de la instancia de Source
. Y aun así, solo hay una garantía de JVM débil. El GC llamará al método de finalize
para close
el recurso. Esto significa que es responsabilidad del cliente llamar explícitamente al método de close
, al igual que es responsabilidad del cliente close
de close
una instancia de java.lang.AutoCloseable
. Para esto, necesitamos una segunda definición del método de uso que maneje scala.io.Source
.
Aquí está el código para esto (aún no siendo conciso):
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()
)
Y aquí hay un ejemplo de uso en un lector de archivos de transmisión de línea súper simple (que actualmente se utiliza para leer archivos delimitados por tabuladores desde la salida de la base de datos):
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
)
)
Se ha proporcionado una versión actualizada de la función anterior como respuesta a una pregunta de diferente pero relacionada .
Ahora, uniendo todo eso con las importaciones extraídas (lo que hace que sea mucho más fácil pegarlo en Scala Worksheet presente tanto en Eclipse ScalaIDE como en el complemento IntelliJ Scala para facilitar el volcado de la salida al escritorio para examinarlo más fácilmente con un editor de texto), así es como se ve el código (con mayor concisión):
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
)
)
Como novato de Scala / FP, me he quemado muchas horas (en su mayoría frustración de rascarse la cabeza) ganando el conocimiento y las soluciones anteriores. Espero que esto ayude a otros principiantes Scala / FP a superar esta joroba de aprendizaje en particular más rápido.
Edición (septiembre de 2011): desde que Eduardo Costa pregunta por Scala2.9, y desde Rick-777 comenta que scalax.IO cometer historia es casi inexistente desde mediados de 2009 ...
Scala-IO ha cambiado de lugar: vea su repositorio GitHub , de Jesse Eichar (también en SO ):
El proyecto paraguas Scala IO consiste en algunos subproyectos para diferentes aspectos y extensiones de IO.
Hay dos componentes principales de Scala IO:
- Core : Core principalmente se ocupa de leer y escribir datos hacia y desde fuentes y sumideros arbitrarios. Los rasgos de la piedra angular son
Input
,Output
ySeekable
que proporcionan la API principal.
Otras clases de importancia sonResource
,ReadChars
yWriteChars
.- Archivo : el archivo es una API de
File
(llamadaPath
) que se basa en una combinación del sistema de archivos Java 7 NIO y las API de SBT PathFinder.
Path
yFileSystem
son los principales puntos de entrada en la API Scala IO File.
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)
Respuesta original (enero de 2011), con el antiguo lugar para scala-io:
Si no quiere esperar a Scala2.9, puede usar la biblioteca scala-incubator / scala-io .
(como se menciona en " ¿Por qué Scala Source no cierra el InputStream subyacente? ")
Ver las muestras
{ // 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)
}
Esta es una de las características que faltan en Scala estándar y que he encontrado tan útil que la agrego a mi biblioteca personal. (Probablemente también deberías tener una biblioteca personal). El código es más o menos así:
def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
val p = new java.io.PrintWriter(f)
try { op(p) } finally { p.close() }
}
y se usa así:
import java.io._
val data = Array("Five","strings","in","a","file!")
printToFile(new File("example.txt")) { p =>
data.foreach(p.println)
}
Esta línea ayuda a escribir un archivo desde una matriz o cadena.
new PrintWriter(outputPath) { write(ArrayName.mkString("")); close }
Para superar a Samthebest y los contribuyentes antes que él, he mejorado la nomenclatura y la concisión:
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))
Si de todos modos tiene Akka Streams en su proyecto, ofrece un diseño único:
def writeToFile(p: Path, s: String)(implicit mat: Materializer): Unit = {
Source.single(ByteString(s)).runWith(FileIO.toPath(p))
}
Akka hace> Streaming File IO
Similar a la respuesta de Rex Kerr, pero más genérica. Primero uso una función auxiliar:
/**
* 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() }
Luego uso esto como:
def writeToFile(fileName:String, data:String) =
using (new FileWriter(fileName)) {
fileWriter => fileWriter.write(data)
}
y
def appendToFile(fileName:String, textData:String) =
using (new FileWriter(fileName, true)){
fileWriter => using (new PrintWriter(fileWriter)) {
printWriter => printWriter.println(textData)
}
}
etc.
Un liners para guardar / leer a / de String
, usando 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
}
Esto no es adecuado para archivos grandes, pero hará el trabajo.
Algunos enlaces:
java.nio.file.Files.write
java.lang.String.getBytes
scala.collection.JavaConverters
scala.collection.immutable.List.mkString
Una micro biblioteca que escribí: github.com/pathikrit/better-files
file.appendLine("Hello", "World")
o
file << "Hello" << "/n" << "World"
Una respuesta simple:
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()
}