Java: leer un archivo en una cadena

En este tutorial, usaremos los métodos Files.lines(), Files.readString(), Files.readAllBytes() y las clases FileReader, BufferedReader y Scanner para leer un archivo en una cadena en Java con ejemplos.

Introducción

En este tutorial, leeremos un archivo en una cadena en Java. Hay algunas maneras en que podemos leer el contenido textual de un archivo.

Aquí hay una lista de todas las clases y métodos que revisaremos:

Files.lines()

La clase Files contiene métodos estáticos para trabajar con archivos y directorios. Un método útil es lines() que devuelve un flujo de cadenas: Stream<String>. A partir de este flujo, se pueden obtener líneas contenidas en un archivo.

El método acepta un Path al archivo que nos gustaría leer con un Charset opcional. Usaremos la sintaxis prueba-con-recursos para automatizar el vaciado y cierre:

1
2
3
4
5
6
7
Path path = Paths.get("input.txt");

try (Stream<String> stream = Files.lines(path, StandardCharsets.UTF_8)) {
    stream.forEach(System.out::println);
} catch (IOException ex) {
    // Handle exception
}

Since the method returns a Stream, we use its para cada() method to iterate over the lines, with a referencia del método for brevity.

En lugar de imprimir cada línea, se puede usar un ‘StringBuilder’ para agregar líneas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Path path = Paths.get("input.txt");

StringBuilder sb = new StringBuilder();

try (Stream<String> stream = Files.lines(path)) {
    stream.forEach(s -> sb.append(s).append("\n"));
    
} catch (IOException ex) {
    // Handle exception
}

String contents = sb.toString();

Con StringBuilder, todo el archivo se puede representar en una sola String (la variable contents anterior). Antes de realizar tales iteraciones, es importante considerar la longitud del archivo de entrada.

Si el archivo no es demasiado grande, está bien ponerlo en una cadena, sin embargo, si tiene un tamaño de cientos de megabytes, no es tan inteligente.

Archivos.readString()

Desde Java 11, la clase Files nos presentó el método readString(), que acepta una Ruta al archivo, así como un Charset.

A diferencia de Files.lines(), devuelve un String directamente, en lugar de un objeto Stream:

1
2
3
4
5
6
7
8
Path path = Paths.get("input.txt");

String contents = null;
try {
    contents = Files.readString(path, StandardCharsets.ISO_8859_1);
} catch (IOException ex) {
    // Handle exception
}

Archivos.readAllBytes()

Un enfoque de lectura de más bajo nivel es el método Files.readAllBytes(), que devuelve un byte[]. Depende del desarrollador usar estos bytes: convertirlos en una cadena, procesarlos tal como son, etc.

Este método también acepta una Ruta al archivo que nos gustaría leer:

1
2
3
4
5
6
7
8
Path path = Paths.get("input.txt");

byte[] bytes = null;
try {
    bytes = Files.readAllBytes(path);
} catch (IOException ex) {
    // Handle exception
}

Ahora, la matriz bytes contiene toda la información del archivo input.txt. La forma más fácil de convertirlo en una Cadena es ponerlos en un constructor con un ‘Charset’ opcional:

1
String str = new String(bytes, StandardCharsets.UTF_8);

Nota: Las soluciones como leer todos los bytes solo son apropiadas en circunstancias en las que estamos tratando con tamaños de archivo pequeños. No es amigable con el rendimiento y no tiene mucho sentido mantener archivos grandes en la memoria del programa.

Escáner

Scanner es una clase particularmente útil para leer contenido de flujos. Dado que funciona con flujos abstractos, también se puede usar para leer cadenas. Scanner funciona dividiendo la entrada en tokens que se recuperan secuencialmente del flujo de entrada.

Como estamos trabajando con cadenas, nos gustaría usar métodos que devuelvan cadenas. Scanner tiene next() y nextLine() exactamente para eso. Ambos métodos devuelven objetos de tipo String. El primero se usa para leer cadenas arbitrarias, mientras que el segundo analiza y devuelve líneas enteras.

Si cada línea contiene la cantidad correcta de datos, nextLine() es una opción ideal. Si hay información importante en el archivo que está dividida en partes más pequeñas pero no necesariamente en líneas (o el archivo contiene, digamos, una sola línea), entonces next() podría ser una mejor opción.

El constructor de Scanner acepta muchos objetos - Paths, InputStreams, Files, etc. Usaremos un File:

1
2
3
4
5
6
File file = new File("input.txt");
Scanner sc = new Scanner(file);

while(sc.hasNext()) {
    System.out.println(sc.next());
}

Estamos usando un bucle while siempre que sc tenga más elementos. Si no verificamos con hasNext(), sc arrojaría una NoSuchElementexception si intentamos acceder a un elemento después del último.

La idea de usar los métodos hasNext() y next() proviene de la interfaz del iterador, ya que Scanner lo implementa internamente.

Lector de archivos

El FileReader se utiliza para leer archivos. Ofrece los métodos read() y read(char[]), que devuelven un solo carácter y varios caracteres respectivamente. Además, acepta un ‘Archivo’ o ‘Cadena’ en el constructor.

FileReader.read(char[])

Abramos un archivo usando FileReader y leamos su contenido:

1
2
3
4
5
6
FileReader in = new FileReader("input.txt");

char[] chars = new char[256];
int n = in.read(chars, 0, chars.length);

String contents = new String(chars);

El método read() acepta una secuencia de caracteres (en la que estamos almacenando los caracteres leídos), el punto de inicio y el punto final de lo que nos gustaría leer. Específicamente, hemos decidido leer 256 caracteres como máximo. Si input.txt tiene más, leeremos solo 256 caracteres. Si tiene menos, se devuelven los caracteres legibles.

El valor devuelto, almacenado dentro del entero n, se puede utilizar para comprobar cuántos caracteres realmente lee el método. En caso de que se haya llegado al final de la secuencia, el método devuelve -1.

Dado que el método llena un char[], podemos convertirlo en una String. Se puede obtener un resultado similar usando String.valueOf(char[]).

Lector de archivos.leer()

El método read(), sin un char[], lee un solo carácter a la vez. Querremos iterar a través de los contenidos y leer cada carácter nosotros mismos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
FileReader in = new FileReader("input.txt");

StringBuilder sb = new StringBuilder();

while(in.read() != -1) {
    sb.append(in.read());
}

String contents = sb.toString();
in.close();

Aquí, verificamos si el carácter de lectura no es -1, lo que indica que no quedan caracteres para leer. Si no, lo ‘agregamos()’ a un ‘StringBuilder’ y, finalmente, lo convertimos a ‘String’.

Nota: Tanto read() como read(char[]) leen bytes, los convierten en caracteres y los devuelven uno por uno. Esto es ineficiente y debe hacerse con buffering cuando sea posible.

Lector almacenado en búfer

BufferedReader es un objeto diseñado para leer texto de un flujo de entrada de caracteres. Está almacenado en un búfer, lo que significa que utiliza un búfer interno para el almacenamiento temporal. Como hemos visto en la sección anterior, los lectores “normales” a veces pueden ser ineficientes.

Se recomienda incluir cualquier ‘Reader’ potencialmente costoso en un ‘BufferedReader’ para aumentar el rendimiento, ya que el almacenamiento en búfer de los caracteres permite una lectura más eficiente del texto de entrada.

Vamos a crear una instancia de BufferedReader:

1
BufferedReader in = new BufferedReader(new FileReader("input.txt"));

En este punto, tenemos un objeto lector almacenado en búfer listo para leer contenidos de input.txt. En este ejemplo, leeremos el archivo línea por línea, aunque BufferedReader admite la lectura de caracteres individuales individualmente y también varios caracteres en una matriz.

Usemos esta instancia de BufferedReader para leer un archivo y almacenar su contenido, línea por línea, en una cadena:

1
2
3
4
5
6
7
8
StringBuilder sb = new StringBuilder();

while(in.readLine != null) {
    sb.append(in.readLine()).append("\n");
}

String contents = sb.toString();
in.close();

Una vez más, estamos usando StringBuilder para recopilar todas las líneas. Para separar cada línea, agregamos un terminador nulo (\n) entre ellas. Finalmente, cerramos la transmisión.

Conclusión

En este artículo, repasamos algunas técnicas comunes para leer archivos en cadenas en Java. Hay muchas opciones, pero la mayoría tiene un principio básico similar: proporciona una ruta al archivo, lee el contenido en una estructura de datos (por ejemplo, char[] o una cadena); luego realice un procesamiento final para recopilar todo el contenido del archivo de manera adecuada.

Hemos cubierto el método File.lines(), el método Files.readString(), el método Files.readAllBytes(), así como Scanner, FileReader y BufferedReader clases

Licensed under CC BY-NC-SA 4.0