Lectura y escritura de archivos

Lectura y escritura de archivos en Kotlin

Anteriormente, cuando nos referimos a la biblioteca estándar de Kotlin para trabajar con flujos de entrada y salida (Input/Output), nos centramos en la transferencia de datos en consola, y ahora vamos a ver cómo trabajar con archivos, y concretamente algunas maneras para leer y escribir sobre ellos.

Lectura de archivos

Antes de entrar en materia, lógicamente necesitamos un archivo para leer; si estamos trabajando con IntelliJ IDEA, podemos crear desde allí un nuevo directorio y crear nuestro archivo de pruebas. Para tener nuestro proyecto ordenado, es habitual llamar a este directorio resources (recursos) y crear dentro de él otros directorios para, por ejemplo, contener imágenes o archivos de texto del proyecto. Para eso, nos situamos sobre la carpeta principal del proyecto y con el botón secundario del ratón seleccionamos New --> Directory:

Lectura y escritura de archivos en Kotlin

En la siguiente ventana le asignamos un nombre, en este caso, 'resources' (opcionalmente también podemos marcar este directorio desde Mark directory as --> Resources Root), y ahora sobre esta carpeta recién creada repetimos los pasos para crear otro directorio llamado 'txt'. Finalmente, sobre la carpeta 'txt' seleccionamos New --> File y le asignamos un nombre y una extensión:

Lectura y escritura de archivos en Kotlin

Ahora tenemos abierto en IntelliJ IDEA el archivo recién creado y escribimos en él cualquier cosa o pegamos un texto, y guardamos los cambios. Para esta entrada y a modo de ejemplo se utiliza el siguiente texto:
Kotlin Doc comparte y divulga en español información y ejemplos sobre programación con Kotlin y Android.
Kotlin es un lenguaje de programación:
- Moderno, conciso y seguro
- Que combina programación funcional y orientada a objetos
- De código abierto
- Interoperable con JAVA
- Divertido de aprender y de escribir

Ahora seleccionando el nuevo archivo podemos ir a Copy relative Path para obtener su ruta relativa, en mi caso: resources/txt/archivo

El paquete kotlin.io ofrece varias maneras de acceder al texto de un archivo, como InputStream, BufferedReader y File, aunque finalmente todas ellas utilizan la extensión java.io.File.

InputStream

import java.io.File
import java.io.InputStream

fun main() {
    val inputStream: InputStream = File("resources/txt/archivo").inputStream()
    val inputString = inputStream.bufferedReader().use { it.readText() }
    println(inputString) // muestra en consola todo el archivo
}

Otra opción:
import java.io.File
import java.io.InputStream
import java.nio.charset.Charset

fun main() {
    val pathName = "resources/txt/archivo"
    val miArchivo = File(pathName)
    val inputStream: InputStream = miArchivo.inputStream()
    val contenido = inputStream.readBytes().toString(Charset.defaultCharset())
    println(contenido) // muestra en pantalla todo el archivo
}

En los dos ejemplos anteriores se crea un InputStream desde un File, con la diferencia de que en el primer caso el contenido se lee con bufferedReader y readText, y en el segundo ejemplo con readBytes y luego los bytes se transforman a texto (String).

Podemos también leer línea por línea:
import java.io.File
import java.io.InputStream

fun main() {
    val inputStream: InputStream = File("resources/txt/archivo").inputStream()

    val lineas = mutableListOf<String>()
    inputStream.bufferedReader().useLines { lines -> lines.forEach { lineas.add(it) } }
    var n = 1
    lineas.forEach { println("${n++} " + it) } // imprime todo el archivo numerando cada línea
}

BufferedReader

import java.io.BufferedReader
import java.io.File

fun main() {
    val bufferedReader: BufferedReader = File("resources/txt/archivo").bufferedReader()
    val inputString = bufferedReader.use { it.readText() }
    println(inputString) // muestra todo el archivo
}

Y para leer línea por línea con BufferedReader:
import java.io.BufferedReader
import java.io.File

fun main() {
    val bufferedReader: BufferedReader = File("resources/txt/archivo").bufferedReader()
    val lineas = mutableListOf<String>()
    bufferedReader.useLines { lines -> lines.forEach { lineas.add(it) } }
    lineas.forEach { println(">  $it") }
}

File

Posiblemente una de las formas más comunes de leer un archivo con Kotlin es usando directamente File con readText:
import java.io.File

fun main() {
    val pathName = "resources/txt/archivo"
    val miArchivo = File(pathName)
    val contenido = miArchivo.readText()
    println(contenido)
}

Un ejemplo de File con readLines y useLines:
import java.io.File

fun main() {

    val pathName = "resources/txt/archivo"
    val miArchivo = File(pathName)

    val lineas = miArchivo.readLines()
    lineas.forEach { println(it) } // muestra todo el archivo

    val lineasLista = mutableListOf<String>()
    miArchivo.useLines { lines -> lines.forEach { lineasLista.add(it) } }
    lineasLista.forEach { println(it) } // muestra todo el archivo
    lineasLista.forEachIndexed { i, line -> println("$i $line") } // numera cada línea
}

Y ahora un ejemplo de Files con readBytes, que lee todo el contenido de un archivo como una matriz de bytes (no se recomienda en archivos grandes), y mostramos el resultado en líneas de diez números:
import java.io.File

fun main() {
    val pathName = "resources/txt/archivo"
    val miArchivo = File(pathName)

    val bytes: ByteArray = miArchivo.readBytes()
    bytes.forEachIndexed { i, byte ->
        when {
            i <= 10 && i != 9 -> print("$byte ")
            i.toString().endsWith("9") -> print("$byte \n")
            else -> print("$byte ")
        }
    }
}

Escritura de archivos

Al igual que para leer archivos, la extensión java.io.File del paquete kotlin.io ofrece varias maneras de escribir un archivo, por ejemplo con PrintWriter, BufferedWriter, writeText y write.

PrintWriter

Podemos crear un archivo en la ruta de nuestro directorio 'resources/txt' de la siguiente manera (printWriter prepara para escribir un archivo y el método use ejecuta el cuerpo de la función en el archivo y luego lo cierra):
import java.io.File

fun main() {
    val outString = "Kotlin Doc\nEscribiendo un archivo con printWriter."
    File("resources/txt/test.txt").printWriter().use { out -> out.println(outString) }
}

Otro ejemplo (comprobamos que sobrescribe el archivo anterior):
import java.io.File

fun main() {
    val ruta = "resources/txt/test.txt"
    val archivo = File(ruta)
    archivo.printWriter().use { out ->
        out.println("Primera línea")
        out.println("Segunda línea")
    }
}

BufferedWriter

import java.io.File

fun main() {
    val outString = "Kotlin Doc\nEscribiendo un archivo con bufferedWriter."
    File("resources/txt/test.txt").bufferedWriter().use { out -> out.write(outString) }
}

Otro ejemplo:
import java.io.File

fun main() {
    val ruta = "resources/txt/test.txt"
    val archivo = File(ruta)
    archivo.bufferedWriter().use { out ->
        out.write("Primera línea\n")
        out.write("Segunda línea\n")
    }
}

writeText

import java.io.File

fun main() {
    val outString = "Kotlin Doc\nEscribiendo un archivo con writeText."
    val archivo = File("resources/txt/test.txt")
    archivo.writeText(outString)
    archivo.appendText("\nLínea 3.")
    archivo.appendText("\nLínea 4.")
}

write

En el siguiente ejemplo la función Files.write() utiliza tres parámetros: la ruta del archivo, el array de bytes para escribir y por último la opción que especifica cómo se abre el archivo. En este caso con APPEND le decimos que añada el contenido al final sin sobrescribir el archivo, por lo que cada vez que ejecutamos el programa se añade nuevo texto al archivo (otras opciones son CREATE, CREATE_NEW, DELETE_ON_CLOSE, READ, WRITE):
import java.io.File
import java.nio.file.Files.write
import java.nio.file.StandardOpenOption

fun main() {
    val outString = "Kotlin Doc\nEscribiendo un archivo con writeText II."
    val archivo = File("resources/txt/test.txt")
    write(archivo.toPath(), outString.toByteArray(), StandardOpenOption.APPEND)
}

Comentarios

Entradas populares

I/O: entrada y salida de datos en consola

Recursos gratis para aprender Kotlin