Ghid pentru BufferedReader

1. Prezentare generală

BufferedReader este o clasă care simplifică citirea textului dintr-un flux de intrare de caractere. Memorizează caracterele pentru a permite citirea eficientă a datelor text.

În acest tutorial, vom analiza modul de utilizare a clasei BufferedReader .

2. Când se utilizează BufferedReader

În general, BufferedReader este util dacă vrem să citim text din orice tip de sursă de intrare, fie că sunt fișiere, socluri sau altceva.

Pur și simplu, ne permite să minimizăm numărul de operații I / O citind bucăți de caractere și stocându-le într-un buffer intern. În timp ce bufferul are date, cititorul va citi din acesta în loc de direct din fluxul de bază.

2.1. Tamponarea unui alt cititor

La fel ca majoritatea claselor Java I / O, BufferedReader implementează modelul Decorator, ceea ce înseamnă că așteaptă un Reader în constructorul său. În acest fel, ne permite să extindem flexibil o instanță a unei implementări Reader cu funcționalitate de tamponare:

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

Dar, dacă tamponarea nu contează pentru noi, putem folosi direct un FileReader :

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

În plus față de buffering, BufferedReader oferă și câteva funcții de asistență frumoase pentru citirea fișierelor linie cu linie . Deci, chiar dacă poate părea mai simplu să utilizați FileReader direct, BufferedReader poate fi de mare ajutor.

2.2. Tamponarea unui flux

În general, putem configura BufferedReader pentru a lua orice tip de flux de intrareca sursă subiacentă . O putem face folosind InputStreamReader și înfășurându-l în constructor:

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

În exemplul de mai sus, citim din System.in care corespunde de obicei intrării de la tastatură. În mod similar, am putea trece un flux de intrare pentru citire dintr-un soclu, fișier sau orice tip imaginabil de intrare textuală. Singura condiție prealabilă este că există o implementare InputStream adecvată pentru aceasta.

2.3. BufferedReader vs Scanner

Ca alternativă, am putea folosi clasa Scanner pentru a obține aceeași funcționalitate ca și cu BufferedReader.

Cu toate acestea, există diferențe semnificative între aceste două clase care le pot face fie mai mult, fie mai puțin convenabile pentru noi, în funcție de cazul nostru de utilizare:

  • BufferedReader este sincronizat (sigur pentru fire) în timp ce scanerul nu este
  • Scannerul poate analiza tipurile și șirurile primitive folosind expresii regulate
  • BufferedReader permite modificarea dimensiunii bufferului în timp ce Scanner are o dimensiune buffer fixă
  • BufferedReader are o dimensiune a bufferului implicită mai mare
  • Scannerul ascunde excepția IOE , în timp ce BufferedReader ne obligă să o gestionăm
  • BufferedReader este de obicei mai rapid decât scanerul, deoarece citește doar datele fără a le analiza

Având în vedere acestea, dacă analizăm jetoane individuale într-un fișier, atunci Scanner se va simți puțin mai natural decât BufferedReader. Dar, doar citirea unei linii la un moment dat este locul în care BufferedReader strălucește.

Dacă este necesar, avem și un ghid despre Scanner .

3. Citirea textului cu BufferedReader

Să parcurgem întregul proces de construire, utilizare și distrugere corectă a unui tampon de citire pentru a citi dintr-un fișier text.

3.1. Inițializarea unui BufferedReader

În primul rând, să creeze un BufferedReader folosind sale (Reader) BufferedReader constructor :

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

Înfășurarea FileReader-ului în acest fel este o modalitate frumoasă de a adăuga buffering ca aspect altor cititori.

În mod implicit, acesta va utiliza un buffer de 8 KB. Cu toate acestea, dacă dorim să tamponăm blocuri mai mici sau mai mari, putem folosi constructorul BufferedReader (Reader, int) :

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

Aceasta va seta dimensiunea bufferului la 16384 octeți (16 KB).

Dimensiunea optimă a bufferului depinde de factori precum tipul fluxului de intrare și hardware-ul pe care rulează codul. Din acest motiv, pentru a atinge dimensiunea tamponului ideal, trebuie să o găsim noi înșine experimentând.

Cel mai bine este să folosiți puteri de 2 ca dimensiune a bufferului, deoarece majoritatea dispozitivelor hardware au o putere de 2 ca dimensiune a blocului.

În cele din urmă, există încă o modalitate la îndemână de a crea un BufferedReader utilizând clasa helper Files din API-ul java.nio :

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

Crearea acestuiaașa este un mod frumos de a tampona dacă vrem să citim un fișier deoarece nu trebuie să creăm mai întâi un FileReader și apoi să-l înfășurăm.

3.2. Citirea rând cu rând

În continuare, să citim conținutul fișierului folosind metoda 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(); }

Putem face același lucru ca mai sus folosind metoda de linii introdusă în Java 8 mai simplu:

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

3.3. Închiderea fluxului

După utilizarea BufferedReader , trebuie să apelăm metoda sa close () pentru a elibera orice resurse de sistem asociate cu acesta. Acest lucru se face automat dacă folosim un bloc de încercare cu resurse :

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

4. Alte metode utile

Acum să ne concentrăm asupra diferitelor metode utile disponibile în BufferedReader.

4.1. Citirea unui singur personaj

Putem folosi metoda read () pentru a citi un singur caracter. Să citim întregul conținut caracter cu caracter până la sfârșitul fluxului:

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

În acest tutorial rapid, am învățat cum să citim fluxurile de intrare a caracterelor pe un exemplu practic folosind BufferedReader .

În cele din urmă, codul sursă pentru exemple este disponibil pe Github.