Guida a BufferedReader

1. Panoramica

BufferedReader è una classe che semplifica la lettura del testo da un flusso di input di caratteri. Memorizza i caratteri per consentire una lettura efficiente dei dati di testo.

In questo tutorial vedremo come utilizzare la classe BufferedReader .

2. Quando utilizzare BufferedReader

In generale, BufferedReader è utile se vogliamo leggere il testo da qualsiasi tipo di sorgente di input, che sia file, socket o qualcos'altro.

In poche parole, ci consente di ridurre al minimo il numero di operazioni di I / O leggendo blocchi di caratteri e memorizzandoli in un buffer interno. Mentre il buffer contiene dati, il lettore leggerà da esso invece che direttamente dal flusso sottostante.

2.1. Buffering di un altro lettore

Come la maggior parte delle classi I / O Java, BufferedReader implementa il pattern Decorator, il che significa che si aspetta un Reader nel suo costruttore. In questo modo, ci consente di estendere in modo flessibile un'istanza di un'implementazione di Reader con funzionalità di buffering:

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

Ma, se il buffering non è importante per noi, potremmo semplicemente usare direttamente un FileReader :

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

Oltre al buffering, BufferedReader fornisce anche alcune belle funzioni di aiuto per leggere i file riga per riga . Quindi, anche se può sembrare più semplice usare FileReader direttamente, BufferedReader può essere di grande aiuto.

2.2. Buffering di un flusso

In generale, possiamo configurare BufferedReader per accettare qualsiasi tipo di flusso di inputcome fonte sottostante . Possiamo farlo usando InputStreamReader e avvolgendolo nel costruttore:

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

Nell'esempio sopra, stiamo leggendo da System.in che corrisponde tipicamente all'input dalla tastiera. Allo stesso modo, potremmo passare un flusso di input per la lettura da un socket, un file o qualsiasi tipo immaginabile di input testuale. L'unico prerequisito è che vi sia un'implementazione di InputStream adeguata .

2.3. BufferedReader vs Scanner

In alternativa, potremmo usare la classe Scanner per ottenere la stessa funzionalità di BufferedReader.

Tuttavia, ci sono differenze significative tra queste due classi che possono renderle più o meno convenienti per noi, a seconda del nostro caso d'uso:

  • BufferedReader è sincronizzato (thread-safe) mentre Scanner no
  • Scanner può analizzare tipi e stringhe primitive utilizzando espressioni regolari
  • BufferedReader consente di modificare la dimensione del buffer mentre Scanner ha una dimensione del buffer fissa
  • BufferedReader ha una dimensione del buffer predefinita maggiore
  • Scanner nasconde IOException , mentre BufferedReader ci costringe a gestirlo
  • BufferedReader è solitamente più veloce di Scanner perché legge solo i dati senza analizzarli

Con questi in mente, se stiamo analizzando singoli token in un file, Scanner sembrerà un po 'più naturale di BufferedReader. Ma, solo leggere una riga alla volta è dove BufferedReader brilla.

Se necessario, abbiamo anche una guida su Scanner .

3. Leggere il testo con BufferedReader

Esaminiamo l'intero processo di creazione, utilizzo e distruzione di un BufferReader correttamente per leggere da un file di testo.

3.1. Inizializzazione di un BufferedReader

Per prima cosa, creiamo un BufferedReader usando il suo costruttore BufferedReader (Reader) :

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

Avvolgere il FileReader in questo modo è un bel modo per aggiungere il buffering come aspetto ad altri lettori.

Per impostazione predefinita, verrà utilizzato un buffer di 8 KB. Tuttavia, se vogliamo bufferizzare blocchi più piccoli o più grandi, possiamo usare il costruttore BufferedReader (Reader, int) :

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

Ciò imposterà la dimensione del buffer a 16384 byte (16 KB).

La dimensione ottimale del buffer dipende da fattori come il tipo di flusso di input e l'hardware su cui è in esecuzione il codice. Per questo motivo, per ottenere la dimensione del buffer ideale, dobbiamo trovarla da soli sperimentando.

È meglio utilizzare potenze di 2 come dimensione del buffer poiché la maggior parte dei dispositivi hardware ha una potenza di 2 come dimensione del blocco.

Infine, c'è un modo più pratico per creare un BufferedReader utilizzando il file classe di supporto dal java.nio API:

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

Crearloin questo modo è un bel modo per bufferizzare se vogliamo leggere un file perché non dobbiamo prima creare manualmente un FileReader e poi avvolgerlo.

3.2. Lettura riga per riga

Successivamente, leggiamo il contenuto del file utilizzando il metodo 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(); }

Possiamo fare la stessa cosa di cui sopra usando il metodo delle linee introdotto in Java 8 un po 'più semplicemente:

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

3.3. Chiusura del flusso

Dopo aver utilizzato BufferedReader , dobbiamo chiamare il suo metodo close () per rilasciare tutte le risorse di sistema ad esso associate. Questo viene fatto automaticamente se usiamo un blocco try-with-resources :

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

4. Altri metodi utili

Ora concentriamoci su vari metodi utili disponibili in BufferedReader.

4.1. Leggere un singolo personaggio

Possiamo usare il metodo read () per leggere un singolo carattere. Leggiamo l'intero contenuto carattere per carattere fino alla fine dello 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(); }

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

In questo breve tutorial, abbiamo imparato come leggere i flussi di input dei caratteri su un esempio pratico utilizzando BufferedReader .

Infine, il codice sorgente per gli esempi è disponibile su Github.