1. Gambaran keseluruhan
BufferedReader adalah kelas yang mempermudah membaca teks dari aliran input watak. Ia menyekat watak untuk membolehkan pembacaan data teks dengan cekap.
Dalam tutorial ini, kita akan melihat cara menggunakan kelas BufferedReader .
2. Bilakah Menggunakan BufferedReader
Secara umum, BufferedReader sangat berguna sekiranya kita ingin membaca teks dari sumber input apa pun sama ada fail, soket, atau yang lain.
Ringkasnya, ini membolehkan kita meminimumkan jumlah operasi I / O dengan membaca potongan watak dan menyimpannya dalam penyangga dalaman. Walaupun penyangga mempunyai data, pembaca akan membaca darinya dan bukannya langsung dari aliran yang mendasari.
2.1. Menyekat Pembaca Lain
Seperti kebanyakan kelas Java I / O, BufferedReader menerapkan corak Decorator, yang bermaksud ia mengharapkan Reader dalam konstruktornya. Dengan cara ini, ini memungkinkan kita untuk memperluas fleksibel pelaksanaan pelaksanaan Pembaca dengan fungsi penyangga:
BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));
Tetapi, jika buffering tidak menjadi masalah bagi kami, kami boleh menggunakan FileReader secara langsung:
FileReader reader = new FileReader("src/main/resources/input.txt");
Selain buffering, BufferedReader juga menyediakan beberapa fungsi pembantu yang bagus untuk membaca fail baris demi baris . Jadi, walaupun kelihatan lebih mudah untuk menggunakan FileReader secara langsung, BufferedReader boleh menjadi pertolongan yang besar.
2.2. Menyekat Aliran
Secara umum, kita dapat mengkonfigurasi BufferedReader untuk mengambil aliran input apa punsebagai sumber yang mendasari . Kita boleh melakukannya dengan menggunakan InputStreamReader dan membungkusnya di konstruktor:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Dalam contoh di atas, kita membaca dari System.in yang biasanya sesuai dengan input dari papan kekunci. Begitu juga, kita dapat meneruskan aliran input untuk membaca dari soket, fail atau jenis input teks yang dapat dibayangkan. Satu-satunya syarat adalah bahawa terdapat pelaksanaan InputStream yang sesuai untuknya .
2.3. BufferedReader vs Pengimbas
Sebagai alternatif, kita dapat menggunakan kelas Scanner untuk mencapai fungsi yang sama seperti pada BufferedReader.
Walau bagaimanapun, terdapat perbezaan yang signifikan antara kedua kelas ini yang dapat menjadikannya lebih senang atau tidak, bergantung kepada kes penggunaan kami:
- BufferedReader diselaraskan (selamatkan benang) sementara Pengimbas tidak
- Pengimbas dapat menguraikan jenis dan rentetan primitif menggunakan ungkapan biasa
- BufferedReader memungkinkan untuk mengubah ukuran buffer sementara Scanner mempunyai ukuran buffer tetap
- BufferedReader mempunyai ukuran penyangga lalai yang lebih besar
- Pengimbas menyembunyikan IOException , sementara BufferedReader memaksa kita untuk mengatasinya
- BufferedReader biasanya lebih pantas daripada Scanner kerana hanya membaca data tanpa menghuraikannya
Dengan ini, jika kita menguraikan token individu dalam fail, maka Pengimbas akan terasa lebih semula jadi daripada BufferedReader. Tetapi, hanya membaca baris pada satu masa di mana BufferedReader bersinar.
Sekiranya diperlukan, kami juga mempunyai panduan mengenai Scanner .
3. Membaca Teks Dengan BufferedReader
Mari kita lalui keseluruhan proses membina, menggunakan dan memusnahkan BufferReader dengan betul untuk membaca dari fail teks.
3.1. Memulakan BufferedReader
Pertama, mari buat BufferedReader menggunakan konstruktor BufferedReader (Reader) :
BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));
Membungkus FileReader seperti ini adalah cara yang baik untuk menambahkan buffering sebagai aspek kepada pembaca lain.
Secara lalai, ini akan menggunakan penyangga 8 KB. Walau bagaimanapun, jika kita ingin menyangga blok yang lebih kecil atau lebih besar, kita boleh menggunakan konstruktor BufferedReader (Reader, int) :
BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt")), 16384);
This will set the buffer size to 16384 bytes (16 KB).
The optimal buffer size depends on factors like the type of the input stream and the hardware on which the code is running. For this reason, to achieve the ideal buffer size, we have to find it ourselves by experimenting.
It's best to use powers of 2 as buffer size since most hardware devices have a power of 2 as the block size.
Finally, there is one more handy way to create a BufferedReader using the Files helper class from the java.nioAPI:
BufferedReader reader = Files.newBufferedReader(Paths.get("src/main/resources/input.txt"))
Creating itlike this is a nice way to buffer if we want to read a file because we don't have to manually create a FileReader first and then wrap it.
3.2. Reading Line-by-Line
Next, let's read the content of the file using the readLine method:
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 a bit more simply:
public String readAllLinesWithStream(BufferedReader reader) { return reader.lines() .collect(Collectors.joining(System.lineSeparator())); }
3.3. Closing the Stream
After using the BufferedReader, we have to call its close() method to release any system resources associated with it. This is done automatically if we use a try-with-resources block:
try (BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"))) { return readAllLines(reader); }
4. Other Useful Methods
Now let's focus on various useful methods available in BufferedReader.
4.1. Reading a Single Character
We can use the read() method to read a single character. Let's read the whole content character-by-character until the end of the 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
Dalam tutorial ringkas ini, kami telah belajar bagaimana membaca aliran input watak pada contoh praktikal menggunakan BufferedReader .
Akhirnya, kod sumber untuk contoh boleh didapati di Github.