Guía de BufferedReader

1. Información general

BufferedReader es una clase que simplifica la lectura de texto de un flujo de entrada de caracteres. Almacena los caracteres para permitir una lectura eficiente de los datos de texto.

En este tutorial, veremos cómo usar la clase BufferedReader .

2. Cuándo usar BufferedReader

En general, BufferedReader es útil si queremos leer texto de cualquier tipo de fuente de entrada, ya sean archivos, sockets u otra cosa.

En pocas palabras, nos permite minimizar el número de operaciones de E / S leyendo fragmentos de caracteres y almacenándolos en un búfer interno. Si bien el búfer tiene datos, el lector leerá de él en lugar de directamente del flujo subyacente.

2.1. Almacenamiento en búfer de otro lector

Como la mayoría de las clases de E / S de Java, BufferedReader implementa el patrón Decorator, lo que significa que espera un Reader en su constructor. De esta manera, nos permite extender de manera flexible una instancia de una implementación de Reader con funcionalidad de almacenamiento en búfer:

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));

Pero, si el almacenamiento en búfer no nos importa, podríamos usar un FileReader directamente:

FileReader reader = new FileReader("src/main/resources/input.txt");

Además del almacenamiento en búfer, BufferedReader también proporciona algunas funciones de ayuda agradables para leer archivos línea por línea . Por lo tanto, aunque puede parecer más sencillo utilizar FileReader directamente, BufferedReader puede ser de gran ayuda.

2.2. Almacenamiento en búfer de una secuencia

En general, podemos configurar BufferedReader para tomar cualquier tipo de flujo de entradacomo fuente subyacente . Podemos hacerlo usando InputStreamReader y envolviéndolo en el constructor:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

En el ejemplo anterior, estamos leyendo de System.in, que normalmente corresponde a la entrada del teclado. De manera similar, podríamos pasar un flujo de entrada para leer desde un conector, archivo o cualquier tipo imaginable de entrada textual. El único requisito previo es que haya una implementación InputStream adecuada para ello.

2.3. Lector con búfer vs escáner

Como alternativa, podríamos usar la clase Scanner para lograr la misma funcionalidad que con BufferedReader.

Sin embargo, existen diferencias significativas entre estas dos clases que pueden hacerlas más o menos convenientes para nosotros, dependiendo de nuestro caso de uso:

  • BufferedReader está sincronizado (seguro para subprocesos) mientras que Scanner no
  • El escáner puede analizar cadenas y tipos primitivos utilizando expresiones regulares
  • BufferedReader permite cambiar el tamaño del búfer, mientras que Scanner tiene un tamaño de búfer fijo
  • BufferedReader tiene un tamaño de búfer predeterminado más grande
  • Scanner oculta IOException , mientras que BufferedReader nos obliga a manejarlo
  • BufferedReader suele ser más rápido que Scanner porque solo lee los datos sin analizarlos

Teniendo esto en cuenta, si estamos analizando tokens individuales en un archivo, Scanner se sentirá un poco más natural que BufferedReader. Pero, solo leyendo una línea a la vez es donde brilla BufferedReader .

Si es necesario, también tenemos una guía sobre el escáner .

3. Leer texto con BufferedReader

Repasemos todo el proceso de creación, uso y destrucción de un BufferReader correctamente para leer un archivo de texto.

3.1. Inicializar un BufferedReader

En primer lugar, creemos un BufferedReader usando su constructor BufferedReader (Reader) :

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));

Envolver el FileReader de esta manera es una buena manera de agregar almacenamiento en búfer como un aspecto para otros lectores.

De forma predeterminada, utilizará un búfer de 8 KB. Sin embargo, si queremos almacenar en búfer bloques más pequeños o más grandes, podemos usar el constructor BufferedReader (Reader, int) :

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt")), 16384);

Esto establecerá el tamaño del búfer en 16384 bytes (16 KB).

El tamaño óptimo del búfer depende de factores como el tipo de flujo de entrada y el hardware en el que se ejecuta el código. Por esta razón, para lograr el tamaño de búfer ideal, tenemos que encontrarlo nosotros mismos experimentando.

Es mejor usar potencias de 2 como tamaño de búfer, ya que la mayoría de los dispositivos de hardware tienen una potencia de 2 como tamaño de bloque.

Finalmente, hay una forma más práctica de crear un BufferedReader usando la clase de ayuda de archivos de la API java.nio :

BufferedReader reader = Files.newBufferedReader(Paths.get("src/main/resources/input.txt"))

Creándolocomo esta es una buena manera de almacenar en búfer si queremos leer un archivo porque no tenemos que crear manualmente un FileReader primero y luego envolverlo.

3.2. Leer línea por línea

A continuación, leamos el contenido del archivo usando el método readLine :

public String readAllLines(BufferedReader reader) throws IOException { StringBuilder content = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { content.append(line); content.append(System.lineSeparator()); } return content.toString(); }

Podemos hacer lo mismo que antes usando el método de líneas introducido en Java 8 de forma un poco más simple:

public String readAllLinesWithStream(BufferedReader reader) { return reader.lines() .collect(Collectors.joining(System.lineSeparator())); }

3.3. Cerrando la corriente

Después de usar BufferedReader , tenemos que llamar a su método close () para liberar los recursos del sistema asociados con él. Esto se hace automáticamente si usamos un bloque try-with-resources :

try (BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"))) { return readAllLines(reader); }

4. Otros métodos útiles

Ahora centrémonos en varios métodos útiles disponibles en BufferedReader.

4.1. Leer un solo carácter

Podemos usar el método read () para leer un solo carácter. Leamos todo el contenido carácter por carácter hasta el final de la transmisión:

public String readAllCharsOneByOne(BufferedReader reader) throws IOException { StringBuilder content = new StringBuilder(); int value; while ((value = reader.read()) != -1) { content.append((char) value); } return content.toString(); }

This will read the characters (returned as ASCII values), cast them to char and append them to the result. We repeat this until the end of the stream, which is indicated by the response value -1 from the read() method.

4.2. Reading Multiple Characters

If we want to read multiple characters at once, we can use the method read(char[] cbuf, int off, int len):

public String readMultipleChars(BufferedReader reader) throws IOException { int length; char[] chars = new char[length]; int charsRead = reader.read(chars, 0, length); String result; if (charsRead != -1) { result = new String(chars, 0, charsRead); } else { result = ""; } return result; }

In the above code example, we'll read up to 5 characters into a char array and construct a string from it. In the case that no characters were read in our read attempt (i.e. we've reached the end of the stream), we'll simply return an empty string.

4.3. Skipping Characters

We can also skip a given number of characters by calling the skip(long n) method:

@Test public void givenBufferedReader_whensSkipChars_thenOk() throws IOException { StringBuilder result = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new StringReader("1__2__3__4__5"))) { int value; while ((value = reader.read()) != -1) { result.append((char) value); reader.skip(2L); } } assertEquals("12345", result); }

In the above example, we read from an input string which contains numbers separated by two underscores. In order to construct a string containing only the numbers, we are skipping the underscores by calling the skip method.

4.4. mark and reset

We can use the mark(int readAheadLimit) and reset() methods to mark some position in the stream and return to it later. As a somewhat contrived example, let's use mark() and reset() to ignore all whitespaces at the beginning of a stream:

@Test public void givenBufferedReader_whenSkipsWhitespacesAtBeginning_thenOk() throws IOException { String result; try (BufferedReader reader = new BufferedReader(new StringReader(" Lorem ipsum dolor sit amet."))) { do { reader.mark(1); } while(Character.isWhitespace(reader.read())) reader.reset(); result = reader.readLine(); } assertEquals("Lorem ipsum dolor sit amet.", result); }

In the above example, we use the mark() method to mark the position we just read. Giving it a value of 1 means only the code will remember the mark for one character forward. It's handy here because, once we see our first non-whitespace character, we can go back and re-read that character without needing to reprocess the whole stream. Without having a mark, we'd lose the L in our final string.

Note that because mark() can throw an UnsupportedOperationException, it's pretty common to associate markSupported() with code that invokes mark(). Though, we don't actually need it here. That's because markSupported() always returns true for BufferedReader.

Of course, we might be able to do the above a bit more elegantly in other ways, and indeed mark and reset aren't very typical methods. They certainly come in handy, though, when there is a need to look ahead.

5. Conclusion

En este tutorial rápido, hemos aprendido cómo leer flujos de entrada de caracteres en un ejemplo práctico usando BufferedReader .

Finalmente, el código fuente de los ejemplos está disponible en Github.