Guide till BufferedReader

1. Översikt

BufferedReader är en klass som förenklar läsning av text från ett teckeninmatningsflöde. Den buffrar tecknen för att möjliggöra effektiv läsning av textdata.

I denna handledning kommer vi att titta på hur man använder BufferedReader klassen .

2. När ska jag använda BufferedReader

I allmänhet kommer BufferedReader till nytta om vi vill läsa text från någon typ av ingångskälla, oavsett om det är filer, uttag eller något annat.

Enkelt uttryckt gör det att vi kan minimera antalet I / O-operationer genom att läsa bitar av tecken och lagra dem i en intern buffert. Medan bufferten har data kommer läsaren att läsa från den istället för direkt från den underliggande strömmen.

2.1. Buffra en annan läsare

Liksom de flesta av Java I / O-klasserna implementerar BufferedReader dekoratormönster , vilket innebär att den förväntar sig en läsare i sin konstruktör. På detta sätt möjliggör det för oss att flexibelt förlänga en instans av en Reader- implementering med buffertfunktionalitet:

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

Men om buffring inte spelar någon roll för oss kan vi bara använda en FileReader direkt:

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

Förutom buffring ger BufferedReader också några trevliga hjälpfunktioner för att läsa filer rad för rad . Så även om det kan verka enklare att använda FileReader direkt kan BufferedReader vara till stor hjälp.

2.2. Buffra en ström

I allmänhet kan vi konfigurera BufferedReader för att ta alla typer av ingångsströmmarsom en underliggande källa . Vi kan göra det med hjälp av InputStreamReader och slå in det i konstruktören:

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

I exemplet ovan läser vi från System.in som vanligtvis motsvarar ingången från tangentbordet. På samma sätt kan vi skicka en inmatningsström för läsning från ett uttag, fil eller vilken som helst tänkbar typ av textinmatning. Den enda förutsättningen är att det finns en lämplig InputStream- implementering för den.

2.3. BufferedReader vs Scanner

Som ett alternativ kan vi använda Scanner- klassen för att uppnå samma funktionalitet som med BufferedReader.

Det finns dock betydande skillnader mellan dessa två klasser som kan göra dem antingen mer eller mindre praktiska för oss, beroende på vårt användningsfall:

  • BufferedReader synkroniseras (trådsäker) medan skannern inte är det
  • Skannern kan analysera primitiva typer och strängar med hjälp av reguljära uttryck
  • BufferedReader gör det möjligt att ändra buffertens storlek medan skannern har en fast buffertstorlek
  • BufferedReader har en större standard buffertstorlek
  • Skannern döljer IOException , medan BufferedReader tvingar oss att hantera det
  • BufferedReader är vanligtvis snabbare än Scanner eftersom den bara läser data utan att analysera den

Med dessa i åtanke, om vi analyserar enskilda tokens i en fil, kommer Skannern att kännas lite mer naturlig än BufferedReader. Men bara att läsa en rad i taget är där BufferedReader lyser.

Om det behövs har vi också en guide om Scanner .

3. Läsa text med BufferedReader

Låt oss gå igenom hela processen med att bygga, använda och förstöra en BufferReader ordentligt för att läsa från en textfil.

3.1. Initiera en buffrad läsare

För det första, låt oss skapa en BufferedReader med dess BufferedReader (Reader) -konstruktör :

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

Att slå in FileReader så här är ett trevligt sätt att lägga till buffring som en aspekt för andra läsare.

Som standard använder detta en buffert på 8 kB. Men om vi vill buffra mindre eller större block kan vi använda BufferedReader (Reader, int) -konstruktören:

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

Detta ställer in buffertstorleken till 16384 byte (16 kB).

Den optimala buffertstorleken beror på faktorer som typen av ingångsström och hårdvaran som koden körs på. Av denna anledning, för att uppnå den ideala buffertstorleken, måste vi hitta den själva genom att experimentera.

Det är bäst att använda krafter på 2 som buffertstorlek eftersom de flesta hårdvaruenheter har en effekt på 2 som blockstorlek.

Slutligen finns det ett praktiskt sätt att skapa en BufferedReader med hjälp av Files helper-klassen från java.nio API:

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

Skapa detså här är ett trevligt sätt att buffra om vi vill läsa en fil eftersom vi inte behöver skapa en FileReader manuellt först och sedan slå in den.

3.2. Läser rad för rad

Låt oss sedan läsa innehållet i filen med readLine- metoden:

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

Vi kan göra samma sak som ovan med linjemetoden som introducerades i Java 8 lite enklare:

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

3.3. Avsluta strömmen

Efter att ha använt BufferedReader måste vi anropa dess close () -metod för att frigöra eventuella systemresurser som är associerade med den. Detta görs automatiskt om vi använder ett försök-med-resurser- block:

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

4. Andra användbara metoder

Låt oss nu fokusera på olika användbara metoder som finns tillgängliga i BufferedReader.

4.1. Läser en enda karaktär

Vi kan använda read () -metoden för att läsa ett enda tecken. Låt oss läsa hela innehållet karaktär för karaktär till slutet av strömmen:

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

I den här snabba handledningen har vi lärt oss hur man läser teckeninmatningsströmmar i ett praktiskt exempel med hjälp av BufferedReader .

Slutligen är källkoden för exemplen tillgänglig på Github.