Guia do BufferedReader

Guia do BufferedReader

1. Visão geral

BufferedReader é uma classe que simplifica a leitura de texto de um fluxo de entrada de caracteres. Ele armazena em buffer os caracteres para permitir uma leitura eficiente dos dados de texto.

Neste tutorial, vamos ver como usar a classeBufferedReader.

2. Quando usarBufferedReader

Em geral,BufferedReader é útil se quisermos ler texto de qualquer tipo de fonte de entrada, seja arquivos, soquetes ou qualquer outra coisa.

Simplificando,it enables us to minimize the number of I/O operations by reading chunks of characters and storing them in an internal buffer. Embora o buffer tenha dados, o leitor lerá dele em vez de diretamente do fluxo subjacente.

2.1. Armazenando outro leitor

Como a maioria das classes de I / O Java,BufferedReader implementsDecorator pattern, meaning it expects a Reader in its constructor. Dessa forma, permite-nos estender com flexibilidade uma instância de uma implementaçãoReader com funcionalidade de buffer:

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

Mas, se o armazenamento em buffer não for importante para nós, podemos apenas usar umFileReader diretamente:

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

Além do armazenamento em buffer,BufferedReader also provides some nice helper functions for reading files line-by-line. Portanto, embora possa parecer mais simples usarFileReader diretamente,BufferedReader pode ser uma grande ajuda.

2.2. Armazenando um Fluxo

Em geral,we can configure BufferedReader to take any kind of input stream as an underlying source. Podemos fazer isso usandoInputStreamReadere envolvendo-o no construtor:

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

No exemplo acima, estamos lendo deSystem.in que normalmente corresponde à entrada do teclado. Da mesma forma, poderíamos transmitir um fluxo de entrada para leitura de um soquete, arquivo ou qualquer tipo imaginável de entrada de texto. O único pré-requisito é que haja uma implementaçãoInputStream adequada para ele.

2.3. BufferedReader vs Scanner

Como alternativa, poderíamos usar a classeScanner para obter a mesma funcionalidade deBufferedReader.

No entanto, existem diferenças significativas entre essas duas classes que podem torná-las mais ou menos convenientes para nós, dependendo do nosso caso de uso:

  • BufferedReader está sincronizado (thread-safe) enquanto o Scanner não está

  • Scanner analisa tipos primitivos e strings usando expressões regulares

  • BufferedReader permite alterar o tamanho do buffer enquanto o Scanner tem um tamanho de buffer fixo

  • BufferedReader tem um tamanho de buffer padrão maior

  • Scanner escondeIOException, enquantoBufferedReader nos força a lidar com isso

  • BufferedReader é geralmente mais rápido do queScanner porque só lê os dados sem analisá-los

Com isso em mente,if we are parsing individual tokens in a file, then Scanner will feel a bit more natural than BufferedReader. But, just reading a line at a time is where BufferedReader shines.

Se necessário, também temosa guide on Scanner.

3. Lendo texto comBufferedReader

Vamos percorrer todo o processo de construção, usando e destruindo umBufferReader rapidamente para ler um arquivo de texto.

3.1. Inicializando umBufferedReader

Em primeiro lugar,let’s create a BufferedReader using its BufferedReader(Reader) constructor:

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

EncapsularFileReader assim é uma boa maneira de adicionar buffering como um aspecto para outros leitores.

Por padrão, isso usará um buffer de 8 KB. No entanto, se quisermos armazenar blocos menores ou maiores, podemos usar o construtorBufferedReader(Reader, int):

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

Isso definirá o tamanho do buffer para 16384 bytes (16 KB).

O tamanho ideal do buffer depende de fatores como o tipo do fluxo de entrada e o hardware no qual o código está sendo executado. Por esse motivo, para atingir o tamanho ideal do buffer, precisamos encontrá-lo experimentando.

É melhor usar potências de 2 como tamanho do buffer, pois a maioria dos dispositivos de hardware tem potências de 2 como tamanho do bloco.

Finalmente,there is one more handy way to create a BufferedReader using the Files helper class dejava.nioAPI:

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

Criá-lo like é uma boa maneira de armazenar em buffer se quisermos ler um arquivo, porque não precisamos criar manualmente umFileReader  primeiro e depois embrulhá-lo.

3.2. Leitura linha a linha

A seguir, vamos ler o conteúdo do arquivo usando o métodoreadLine:

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

We can do the same thing as above using the lines method introduced in Java 8 um pouco mais simples:

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

3.3. Fechando o Fluxo

Depois de usarBufferedReader, devemos chamar seu métodoclose() para liberar quaisquer recursos do sistema associados a ele. Isso é feito automaticamente se usarmos um blocotry-with-resources:

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

4. Outros métodos úteis

Agora vamos nos concentrar em vários métodos úteis disponíveis emBufferedReader.

4.1. Lendo um único caractere

Podemos usar o métodoread() para ler um único caractere. Vamos ler todo o conteúdo caractere por caractere até o final do stream:

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

Isso irá ler os caracteres (retornados como valores ASCII), convertê-los emchare acrescentá-los ao resultado. Repetimos isso até o final do fluxo, o que é indicado pelo valor de resposta -1 do métodoread().

4.2. Lendo vários caracteres

Se quisermos ler vários caracteres de uma vez, podemos usar o métodoread(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;
}

No exemplo de código acima, vamos ler até 5 caracteres em uma matriz char e construir uma string a partir dela. No caso em que nenhum caractere foi lido em nossa tentativa de leitura (ou seja, chegamos ao final do fluxo), vamos simplesmente retornar uma string vazia.

4.3. Pular personagens

Também podemos pular um determinado número de caracteres chamando o métodoskip(long n):

@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);
}

No exemplo acima, lemos a partir de uma sequência de entrada que contém números separados por dois sublinhados. Para construir uma string contendo apenas os números, estamos pulando os sublinhados chamando o métodoskip .

4.4. mark ereset

Podemos usar os métodosmark(int readAheadLimit)ereset() para marcar alguma posição no fluxo e retornar a ela mais tarde. Como um exemplo um tanto artificial, vamos usarmark()ereset() para ignorar todos os espaços em branco no início de um fluxo:

@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);
}

No exemplo acima, usamos o métodomark() para marcar a posição que acabamos de ler. Atribuir a ele um valor 1 significa que apenas o código lembrará a marca de um caractere para a frente. 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.

Observe que, comomark() pode lançar umUnsupportedOperationException, é muito comum associarmarkSupported() com código que invocamark(). . Embora, na verdade, não precisamos dele aqui. That’s because markSupported() always returns true for BufferedReader.

Claro, podemos ser capazes de fazer o acima com um pouco mais de elegância de outras maneiras e, de fato,mark andreset não são métodos muito típicos. They certainly come in handy, though, when there is a need to look ahead.

5. Conclusão

Neste tutorial rápido, aprendemos como ler fluxos de entrada de caracteres em um exemplo prático usandoBufferedReader.

Finalmente, o código-fonte dos exemplos está disponívelover on Github.