Guide to BufferedReader – 缓冲读卡器指南

最后修改: 2018年 11月 7日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

BufferedReader is a class which simplifies reading text from a character input stream. It buffers the characters in order to enable efficient reading of text data.

BufferedReader是一个简化从字符输入流中读取文本的类。它对字符进行了缓冲,以便能够有效地读取文本数据。

In this tutorial, we’re going to look at how to use the BufferedReader class.

在本教程中,我们将看看如何使用BufferedReader

2. When to Use BufferedReader

2.何时使用BufferedReader

In general, BufferedReader comes in handy if we want to read text from any kind of input source whether that be files, sockets, or something else.

一般来说,如果我们想从任何类型的输入源读取文本,无论是文件、套接字还是其他东西,BufferedReader都会很方便。

Simply put, it enables us to minimize the number of I/O operations by reading chunks of characters and storing them in an internal buffer. While the buffer has data, the reader will read from it instead of directly from the underlying stream.

简单地说,它使我们能够通过读取字符块并将其存储在一个内部缓冲区中,从而最大限度地减少I/O操作的数量。当缓冲区有数据时,阅读器将从其中读取,而不是直接从底层流中读取。

2.1. Buffering Another Reader

2.1.缓冲另一个读者

Like most of the Java I/O classes, BufferedReader implements Decorator pattern, meaning it expects a Reader in its constructor. In this way, it enables us to flexibly extend an instance of a Reader implementation with buffering functionality:

像大多数Java I/O类一样,BufferedReader 实现了Decorator模式,意味着它在构造函数中期望有一个Reader通过这种方式,它使我们能够灵活地扩展具有缓冲功能的Reader实现的实例。

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

But, if buffering doesn’t matter to us we could just use a FileReader directly:

但是,如果缓冲对我们来说并不重要,我们可以直接使用文件读取器

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

In addition to buffering, BufferedReader also provides some nice helper functions for reading files line-by-line. So, even though it may appear simpler to use FileReader directly, BufferedReader can be a big help.

除了缓冲之外,BufferedReader 还提供了一些很好的辅助函数来逐行读取文件。因此,尽管直接使用FileReader 可能看起来更简单,但BufferedReader 可以提供很大帮助。

2.2. Buffering a Stream

2.2.缓冲一个流

In general, we can configure BufferedReader to take any kind of input stream as an underlying source. We can do it using InputStreamReader and wrapping it in the constructor:

一般来说,我们可以配置BufferedReader来接受任何种类的输入流作为底层源。我们可以使用InputStreamReader并将其包装在构造函数中。

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

In the above example, we are reading from System.in which typically corresponds to the input from the keyboard. Similarly, we could pass an input stream for reading from a socket, file or any imaginable type of textual input. The only prerequisite is that there is a suitable InputStream implementation for it.

在上面的例子中,我们从System.in读取,这通常对应于来自键盘的输入。同样地,我们也可以通过一个输入流来读取socket、文件或任何可以想象到的文本输入类型。唯一的前提是有一个合适的InputStream实现来实现它。

2.3. BufferedReader vs Scanner

2.3.缓冲读卡器与扫描器

As an alternative, we could use the Scanner class to achieve the same functionality as with BufferedReader.

作为替代,我们可以使用Scanner类来实现与BufferedReader.相同的功能。

However, there are significant differences between these two classes which can make them either more or less convenient for us, depending on our use case:

然而,这两个类之间有显著的区别,这可能使它们对我们来说更方便或不方便,这取决于我们的使用情况。

  • BufferedReader is synchronized (thread-safe) while Scanner is not
  • Scanner can parse primitive types and strings using regular expressions
  • BufferedReader allows for changing the size of the buffer while Scanner has a fixed buffer size
  • BufferedReader has a larger default buffer size
  • Scanner hides IOException, while BufferedReader forces us to handle it
  • BufferedReader is usually faster than Scanner because it only reads the data without parsing it

With these in mind, if we are parsing individual tokens in a file, then Scanner will feel a bit more natural than BufferedReader. But, just reading a line at a time is where BufferedReader shines.

考虑到这些,如果我们正在解析文件中的单个标记,那么Scanner将比BufferedReader感觉更自然一些。但是,一次只读一行是BufferedReader的闪光点。

If needed, we also have a guide on Scanner as well.

如果需要,我们也有关于Scanner的指南。

3. Reading Text With BufferedReader

3.用BufferedReader读取文本

Let’s go through the entire process of building, using and destroying a BufferReader properly to read from a text file.

让我们来看看构建、使用和销毁BufferReader的整个过程,以正确地从一个文本文件中读取。

3.1. Initializing a BufferedReader

3.1.初始化一个BufferedReader

Firstly, let’s create a BufferedReader using its BufferedReader(Reader) constructor:

首先,让我们使用其BufferedReader(Reader)构造函数创建一个BufferedReader

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

Wrapping the FileReader like this is a nice way to add buffering as an aspect to other readers.

像这样包装FileReader是一种很好的方式,可以将缓冲作为一个方面加入到其他阅读器中。

By default, this will use a buffer of 8 KB. However, if we want to buffer smaller or larger blocks, we can use the BufferedReader(Reader, int) constructor:

默认情况下,这将使用一个8KB的缓冲区。然而,如果我们想缓冲更小或更大的块,我们可以使用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).

这将设置缓冲区大小为16384字节(16KB)。

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.

最好使用2的幂作为缓冲区的大小,因为大多数硬件设备的块大小都是2的幂。

Finally, there is one more handy way to create a BufferedReader using the Files helper class from the java.nio API:

最后,还有一个方便的方法,使用Files帮助类java.nioAPI:创建一个BufferedReader

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

Creating it like 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.

像这样创建是一种很好的缓冲方式,如果我们想读取一个文件,因为我们不必先手动创建一个FileReader,然后再将其包裹。

3.2. Reading Line-by-Line

3.2.逐行阅读

Next, let’s read the content of the file using the readLine method:

接下来,让我们使用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();
}

We can do the same thing as above using the lines method introduced in Java 8 a bit more simply:

我们可以使用Java 8中引入的lines 方法,更简单地完成上述同样的事情

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

3.3. Closing the Stream

3.3.关闭水流

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:

在使用完BufferedReader后,我们必须调用其close()方法来释放与之相关的任何系统资源。如果我们使用一个try-with-resources块,这将自动完成。

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

4. Other Useful Methods

4.其他有用的方法

Now let’s focus on various useful methods available in BufferedReader.

现在让我们关注一下BufferedReader.中的各种有用方法。

4.1. Reading a Single Character

4.1.读取单个字符

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:

我们可以使用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.

这将读取字符(以ASCII值的形式返回),将其转换为char 并将其追加到结果中。我们重复这样做,直到流的结束,即通过read()方法的响应值-1来表示。

4.2. Reading Multiple Characters

4.2.阅读多个字符

If we want to read multiple characters at once, we can use the method read(char[] cbuf, int off, int len):

如果我们想一次读取多个字符,我们可以使用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.

在上面的代码例子中,我们最多读取5个字符到一个char数组中,并从中构建一个字符串。如果在我们的读取尝试中没有读到任何字符(即我们已经到达流的末端),我们将简单地返回一个空字符串。

4.3. Skipping Characters

4.3.跳过字符

We can also skip a given number of characters by calling the skip(long n) method:

我们还可以通过调用skip(long n)方法来跳过指定的字符数。

@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.

在上面的例子中,我们从一个输入字符串中读出了由两个下划线分隔的数字。为了构建一个只包含数字的字符串,我们通过调用skip方法跳过了下划线。

4.4. mark and reset

4.4.标记复位

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:

我们可以使用mark(int readAheadLimit)reset()方法来标记流中的某个位置,并在以后返回。作为一个有点矫揉造作的例子,让我们使用mark()reset()来忽略一个流开始时的所有空白处。

@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.

在上面的例子中,我们使用mark()方法来标记我们刚刚读取的位置。给它一个1的值意味着只有代码会记住向前一个字符的标记。它在这里很方便,因为一旦我们看到第一个非空白字符,我们就可以回去重新读那个字符,而不需要重新处理整个数据流。如果没有标记,我们就会失去最终字符串中的L

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.

请注意,由于mark()可以抛出UnsupportedOperationException,所以将markSupported()与调用mark()的代码联系起来是相当普遍的。尽管如此,我们在这里实际上并不需要它。这是因为markSupported() 总是为BufferedReader返回true。

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.

当然,我们也许可以通过其他方式更优雅地完成上述工作,事实上markreset并不是非常典型的方法。不过,当需要向前看时,它们肯定会派上用场

5. Conclusion

5.结论

In this quick tutorial, we’ve learned how to read character input streams on a practical example using BufferedReader.

在这个快速教程中,我们已经学会了如何使用BufferedReader在一个实际的例子上读取字符输入流。

Finally, the source code for the examples is available over on Github.

最后,这些例子的源代码可以在Github上找到