Anleitung zu BufferedReader

1. Übersicht

BufferedReader ist eine Klasse, die das Lesen von Text aus einem Zeicheneingabestream vereinfacht. Es puffert die Zeichen, um ein effizientes Lesen von Textdaten zu ermöglichen.

In diesem Tutorial sehen wir uns an, wie die BufferedReader- Klasse verwendet wird .

2. Wann wird BufferedReader verwendet ?

Im Allgemeinen ist BufferedReader nützlich, wenn wir Text aus einer beliebigen Eingabequelle lesen möchten, egal ob es sich um Dateien, Sockets oder etwas anderes handelt.

Einfach ausgedrückt, können wir die Anzahl der E / A-Vorgänge minimieren, indem wir Zeichenblöcke lesen und in einem internen Puffer speichern. Während der Puffer Daten enthält, liest der Leser daraus und nicht direkt aus dem zugrunde liegenden Stream.

2.1. Einen anderen Leser puffern

Wie die meisten Java-E / A-Klassen implementiert BufferedReader das Decorator-Muster, dh es erwartet einen Reader in seinem Konstruktor. Auf diese Weise können wir eine Instanz einer Reader- Implementierung flexibel mit Pufferfunktionen erweitern:

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

Wenn uns das Puffern jedoch nichts ausmacht , können wir einfach einen FileReader direkt verwenden:

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

Neben der Pufferung bietet BufferedReader auch einige nützliche Hilfsfunktionen zum zeilenweisen Lesen von Dateien . Also, auch wenn kann es einfacher erscheinen verwenden Filereader direkt, BufferedReader kann eine große Hilfe sein.

2.2. Einen Stream puffern

Im Allgemeinen können wir BufferedReader so konfigurieren , dass jede Art von Eingabestream verwendet wirdals zugrunde liegende Quelle . Wir können dies mit InputStreamReader tun und es in den Konstruktor einschließen :

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

Im obigen Beispiel lesen wir aus System.in, was normalerweise der Eingabe über die Tastatur entspricht. In ähnlicher Weise könnten wir einen Eingabestream zum Lesen aus einem Socket, einer Datei oder einer beliebigen denkbaren Art von Texteingabe übergeben. Einzige Voraussetzung ist, dass dafür eine geeignete InputStream- Implementierung vorhanden ist.

2.3. BufferedReader vs Scanner

Alternativ könnten wir die Scanner- Klasse verwenden, um die gleiche Funktionalität wie mit BufferedReader zu erreichen.

Es gibt jedoch signifikante Unterschiede zwischen diesen beiden Klassen, die sie je nach Anwendungsfall für uns mehr oder weniger bequem machen können:

  • BufferedReader ist synchronisiert (threadsicher), Scanner nicht
  • Der Scanner kann primitive Typen und Zeichenfolgen mithilfe regulärer Ausdrücke analysieren
  • Mit BufferedReader können Sie die Größe des Puffers ändern, während der Scanner eine feste Puffergröße hat
  • BufferedReader hat eine größere Standardpuffergröße
  • Der Scanner verbirgt die IOException , während BufferedReader uns zwingt, damit umzugehen
  • BufferedReader ist normalerweise schneller als Scanner, da nur die Daten gelesen werden, ohne sie zu analysieren

In diesem Sinne fühlt sich der Scanner beim Parsen einzelner Token in einer Datei etwas natürlicher an als der BufferedReader. Wenn Sie jedoch jeweils nur eine Zeile lesen, leuchtet BufferedReader .

Bei Bedarf haben wir auch eine Anleitung zum Scanner .

3. Lesen von Text mit BufferedReader

Lassen Sie uns den gesamten Prozess des Erstellens , Verwendens und Zerstörens eines BufferReaders zum Lesen aus einer Textdatei durchlaufen .

3.1. Initialisieren eines BufferedReader

Zunächst werfen wir einen erstellen BufferedReader mit seiner BufferedReader (Reader) Konstruktor :

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

Das Umschließen des FileReader ist eine gute Möglichkeit, anderen Lesern Pufferung als Aspekt hinzuzufügen.

Standardmäßig wird ein Puffer von 8 KB verwendet. Wenn wir jedoch kleinere oder größere Blöcke puffern möchten, können wir den Konstruktor BufferedReader (Reader, int) verwenden:

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

Dadurch wird die Puffergröße auf 16384 Byte (16 KB) festgelegt.

Die optimale Puffergröße hängt von Faktoren wie dem Typ des Eingabestreams und der Hardware ab, auf der der Code ausgeführt wird. Um die ideale Puffergröße zu erreichen, müssen wir sie aus diesem Grund selbst durch Experimentieren finden.

Es ist am besten, Potenzen von 2 als Puffergröße zu verwenden, da die meisten Hardwaregeräte eine Potenz von 2 als Blockgröße haben.

Schließlich ist es eine weitere praktische Möglichkeit , einen erstellen BufferedReader mit der Dateien Helper - Klasse von der java.nio API:

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

ErstellenDies ist eine gute Möglichkeit zum Puffern, wenn wir eine Datei lesen möchten, da wir nicht zuerst manuell einen FileReader erstellen und ihn dann umbrechen müssen .

3.2. Zeile für Zeile lesen

Als nächstes lesen wir den Inhalt der Datei mit der readLine- Methode:

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

Wir können das Gleiche tun wie oben unter Verwendung der Linien - Methode in Java 8 eingeführt ein Bit mehr einfach:

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

3.3. Stream schließen

Nachdem wir den BufferedReader verwendet haben , müssen wir seine close () -Methode aufrufen , um alle damit verbundenen Systemressourcen freizugeben. Dies erfolgt automatisch, wenn wir einen Try-with-Resources- Block verwenden:

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

4. Andere nützliche Methoden

Konzentrieren wir uns nun auf verschiedene nützliche Methoden, die in BufferedReader verfügbar sind .

4.1. Ein einzelnes Zeichen lesen

Wir können die read () -Methode verwenden, um ein einzelnes Zeichen zu lesen. Lesen wir den gesamten Inhalt zeichenweise bis zum Ende des Streams:

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 diesem kurzen Tutorial haben wir anhand eines praktischen Beispiels mit BufferedReader gelernt, wie man Zeicheneingabestreams liest .

Schließlich ist der Quellcode für die Beispiele auf Github verfügbar.