Ръководство за BufferedReader

1. Общ преглед

BufferedReader е клас, който опростява четенето на текст от поток за въвеждане на символи. Той буферира символите, за да позволи ефективно четене на текстови данни.

В този урок ще разгледаме как да използваме класа BufferedReader .

2. Кога да се използва BufferedReader

По принцип BufferedReader е полезен, ако искаме да четем текст от всякакъв вид входен източник, независимо дали това са файлове, сокети или нещо друго.

Най-просто казано, това ни позволява да минимизираме броя на I / O операциите, като четем парчета символи и ги съхраняваме във вътрешен буфер. Докато буферът има данни, четецът ще чете от него, вместо директно от основния поток.

2.1. Буфериране на друг четец

Подобно на повечето I / O класове на Java, BufferedReader реализира шаблон на Decorator, което означава, че очаква Reader в своя конструктор. По този начин тя ни позволява гъвкаво да разширяваме екземпляр на реализация на Reader с буферна функционалност:

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

Но ако буферирането няма значение за нас, можем просто да използваме FileReader директно:

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

В допълнение към буферирането, BufferedReader предлага и някои приятни помощни функции за четене на файлове ред по ред . Така че, въпреки че може да изглежда по-просто да се използва FileReader директно, BufferedReader може да бъде от голяма помощ.

2.2. Буфериране на поток

Като цяло можем да конфигурираме BufferedReader да приема какъвто и да е входен потоккато основен източник . Можем да го направим с помощта на InputStreamReader и да го увием в конструктора:

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

В горния пример четем от System.in, което обикновено съответства на входа от клавиатурата. По същия начин бихме могли да предадем входен поток за четене от сокет, файл или какъвто и да е въображаем тип текстов вход. Единственото условие е да има подходяща реализация на InputStream за него.

2.3. BufferedReader срещу скенер

Като алтернатива бихме могли да използваме класа Scanner, за да постигнем същата функционалност като при BufferedReader.

Съществуват обаче съществени разлики между тези два класа, които могат да ги направят или повече или по-малко удобни за нас, в зависимост от нашия случай на употреба:

  • BufferedReader е синхронизиран (безопасен за нишки), докато Scanner не е
  • Скенерът може да анализира примитивни типове и низове, използвайки регулярни изрази
  • BufferedReader позволява промяна на размера на буфера, докато Scanner има фиксиран размер на буфера
  • BufferedReader има по-голям размер на буфера по подразбиране
  • Скенерът скрива IOException , докато BufferedReader ни принуждава да се справим
  • BufferedReader обикновено е по-бърз от Scanner, защото само чете данните, без да ги анализира

Имайки предвид това, ако анализираме отделни маркери във файл, тогава Scanner ще се почувства малко по-естествено от BufferedReader. Но само четенето на ред в даден момент е мястото, където BufferedReader блести.

Ако е необходимо, имаме ръководство и за скенера .

3. Четене на текст с BufferedReader

Нека да преминем през целия процес на изграждане, използване и унищожаване на BufferReader правилно за четене от текстов файл.

3.1. Инициализиране на BufferedReader

Първо, нека създадем BufferedReader, използвайки своя конструктор BufferedReader (Reader) :

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

Опаковането на FileReader по този начин е добър начин да добавите буфериране като аспект към други четци.

По подразбиране това ще използва буфер от 8 KB. Ако обаче искаме да буферираме по-малки или по-големи блокове, можем да използваме конструктора BufferedReader (Reader, int) :

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

Това ще зададе размера на буфера на 16384 байта (16 KB).

Оптималният размер на буфера зависи от фактори като вида на входния поток и хардуера, на който се изпълнява кодът. Поради тази причина, за да постигнем идеалния размер на буфера, трябва да го намерим сами, като експериментираме.

Най-добре е да използвате мощности 2 като размер на буфера, тъй като повечето хардуерни устройства имат мощност 2 като размер на блока.

И накрая, има още един удобен начин за създаване на BufferedReader с помощта на помощния клас Files от API на java.nio :

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

Създавайки гокато това е хубав начин да буферираме, ако искаме да прочетем файл, защото не е необходимо първо да създаваме FileReader ръчно и след това да го обвиваме.

3.2. Четене ред по ред

След това нека прочетем съдържанието на файла, използвайки метода 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(); }

Можем да направим същото, както по-горе, като използваме метода за линии , въведен в Java 8 малко по-просто:

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

3.3. Затваряне на потока

След като използваме BufferedReader , трябва да извикаме неговия метод close () , за да освободим всички системни ресурси, свързани с него. Това се прави автоматично, ако използваме блок try-with-resources :

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

4. Други полезни методи

Сега да се съсредоточим върху различни полезни методи, налични в BufferedReader.

4.1. Четене на един символ

Можем да използваме метода read () , за да прочетем един знак. Нека прочетем цялото съдържание по символи до края на потока:

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

В този бърз урок научихме как да четем потоци за въвеждане на символи на практически пример, използвайки BufferedReader .

И накрая, изходният код за примерите е достъпен в Github.