Java BufferedInputStream

时间:2020-01-09 10:36:00  来源:igfitidea点击:

Java BufferedInputStream类java.io.BufferedInputStream为Java InputStream(包括InputStream的任何子类)提供字节块的透明读取和缓冲。读取更大的字节块并对其进行缓冲可以大大提高IO的速度。 BufferedInputStream并非一次从网络或者磁盘读取一个字节,而是一次将一个较大的块读取到内部缓冲区中。因此,当我们从Java BufferedInputStream读取一个字节时,我们将从其内部缓冲区读取它。缓冲区被完全读取后,BufferedInputStream将另一个更大的数据块读入缓冲区。这通常比一次从InputStream读取单个字节要快得多,尤其是对于磁盘访问和更大的数据量而言。

Java BufferedInputStream示例

要将缓冲添加到" InputStream"中,只需将其包装在" BufferedInputStream"中即可。看起来是这样的:

BufferedInputStream bufferedInputStream = new BufferedInputStream(
                      new FileInputStream("c:\data\input-file.txt"));

如我们所见,使用" BufferedInputStream"将缓冲添加到非缓冲的" InputStream"非常简单。 " BufferedInputStream"在内部创建一个" byte"数组,并尝试通过在底层" InputStream"上调用" InputStream.read(byte [])"方法来填充该数组。

设置BufferedInputStream的缓冲区大小

我们可以设置缓冲区大小以供JavaBufferedInputStream内部使用。我们可以将缓冲区大小作为参数提供给BufferedInputStream构造函数,如下所示:

int bufferSize = 8 * 1024;
    
BufferedInputStream bufferedInputStream = new BufferedInputStream(
                      new FileInputStream("c:\data\input-file.txt"),
                      bufferSize
    );

这个例子将'BufferedInputStream'使用的内部缓冲区设置为8 KB。最好使用1024个字节的倍数的缓冲区大小。最适用于硬盘等大多数内置缓冲。

除了为输入流添加缓冲之外,BufferedInputStream的行为与InputStream的行为完全相同。

BufferedInputStream的最佳缓冲区大小

我们应该使用不同的缓冲区大小进行一些实验,以找出哪种缓冲区大小似乎可以在具体硬件上提供最佳性能。最佳缓冲区大小可能取决于我们将JavaBufferedInputStream与磁盘一起使用还是与网络InputStream一起使用。

对于磁盘流和网络流,最佳缓冲区大小也可能取决于计算机中的具体硬件。如果硬盘一次至少读取4KB,那么使用少于4KB的缓冲区是愚蠢的。然后最好使用4KB的倍数的缓冲区大小。例如,使用6KB也是愚蠢的。

即使磁盘读取例如一次使用4KB,最好使用大于此大小的缓冲区。磁盘擅长顺序读取数据,这意味着它擅长读取彼此相邻的多个块。因此,与仅使用4KB缓冲区相比,将16KB缓冲区或者64KB缓冲区(或者更大)与BufferedInputStream一起使用仍然可以提供更好的性能。

还请记住,某些硬盘具有一些兆字节的读取缓存。如果硬盘仍然将文件读取到内部缓存中,例如64KB,那么我们最好使用一次读取操作而不是使用多次读取操作将所有数据都放入" BufferedInputStream"中。多次读取操作将变慢,并且我们冒着硬盘的读取缓存在读取操作之间被擦除的风险,从而导致硬盘将该块重新读取到缓存中。

要找到最佳的" BufferedInputStream"缓冲区大小,请找出硬盘读取的块大小,并可能还要找到其缓存大小,然后将缓冲区设为该大小的倍数。我们肯定必须尝试找到最佳的缓冲区大小。通过测量具有不同缓冲区大小的读取速度来做到这一点。

mark()和reset()

关于BufferedInputStream的一个有趣的方面是它支持从InputStream继承的mark()和reset()方法。并非所有的InputStream子类都支持这些方法。通常,我们可以调用" markSupported()"方法来查找给定的" InputStream"是否支持" mark()"和" reset()",但是" BufferedInputStream"支持它们。

关闭BufferedInputStream

当我们从Java的" BufferedInputStream"读取完数据后,必须将其关闭。通过调用从InputStream继承的close()方法来关闭BufferedInputStream。关闭Java的" BufferedInputStream"也会关闭" BufferedInputStream"正在从中读取和缓冲数据的" InputStream"。这是一个打开JavaBufferedInputStream,从中读取所有数据,然后关闭它的示例:

BufferedInputStream bufferedInputStream = new BufferedInputStream(
                      new FileInputStream("c:\data\input-file.txt"));

int data = bufferedInputStream.read();
while(data != -1) {
  data = bufferedInputStream.read();
}
bufferedInputStream.close();

注意while循环如何继续,直到从BufferedInputStream的read()方法中读取-1为止。之后,while循环退出,并调用BufferedInputStreamclose()方法。

上面的代码不是100%健壮的。如果从" BufferedInputStream"读取数据时引发了异常,则永远不会调用" close()"方法。为了使代码更健壮,我们将必须使用Java Java尝试使用资源构造。我在Java IO异常处理的教程中也解释了使用Java IO类的正确异常处理。

这是一个使用try-with-resources构造关闭JavaBufferedInputStream的示例:

try(BufferedInputStream bufferedInputStream =
        new BufferedInputStream( new FileInputStream("c:\data\input-file.txt") ) ) {

    int data = bufferedInputStream.read();
    while(data != -1){
        data = bufferedInputStream.read();
    }
}

注意,在" try"关键字之后的括号内声明了" BufferedInputStream"。这向Java发出信号,该" BufferedInputStream"将由try-with-resources构造进行管理。

一旦执行线程退出" try"块," BufferedInputStream"将关闭。如果从try块内部抛出异常,则捕获该异常,关闭BufferedInputStream,然后重新抛出该异常。因此,当在try-with-resources块中使用时,可以确保BufferedInputStream是关闭的。

可重用的BufferedInputStream

标准Java" BufferedInputStream"的缺点之一是只能使用一次。一旦关闭它,它就不再可用。如果需要读取大量文件或者网络流,则必须为要读取的每个文件或者网络流创建一个新的BufferedInputStream。这意味着我们要创建一个新对象,更重要的是,要创建一个新的字节数组,该数组将用作BufferedInputStream中的缓冲区。如果读取的文件或者流的数量很高,并且彼此之间读取很快,这可能会对Java垃圾收集器造成压力。

一种替代方法是创建一个可重用的BufferedInputStream,其中我们可以替换基础源" InputStream",以便可以重用BufferedInputStream及其内部字节数组缓冲区。为了节省麻烦,我创建了这样的ReusableBufferedInputStream,并将其代码包含在本教程的后面。首先,我想向我们展示如何使用此" ReusableBufferedInputStream"外观。

创建一个ReusableBufferedInputStream

首先,我们需要创建一个" ReusableBufferedInputStream"。这是一个如何创建ReusableBufferedInputStream的示例:

ReusableBufferedInputStream reusableBufferedInputStream =
    new ReusableBufferedInputStream(new byte[1024 * 1024]);

本示例创建一个具有1 MB字节数组作为其内部缓冲区的ReusableBufferedInputStream

设定来源

创建ReusableBufferedInputStream之后,我们需要在其上设置InputStream用作基础数据源。这是在ReusableBufferedInputStream上设置源InputStream的方法:

FileInputStream inputStream = new FileInputStream("/mydata/somefile.txt");

reusableBufferedInputStream.setSource(inputStream);

setSource()方法实际上返回对ReusableBufferedInputStream的引用,因此我们实际上可以创建ReusableBufferedInputStream并在一条指令中设置源:

ReusableBufferedInputStream reusableBufferedInputStream =
    new ReusableBufferedInputStream(new byte[1024 * 1024])
        .setSource(new FileInputStream("/mydata/somefile.txt"));

重用ReusableBufferedInputStream

使用完ReusableBufferedInputStream之后,我们需要关闭它。关闭它只会关闭底层的源InputStream。关闭ReusableBufferedInputStream之后,我们可以再次使用它,只需在其上设置新的源InputStream。重用ReusableBufferedInputStream的样子如下:

reusableBufferedInputStream.setSource(new FileInputStream("/mydata/file-1.txt"));

//read data from ReusableBufferedInputStream

reusableBufferedInputStream.close();

reusableBufferedInputStream.setSource(new FileInputStream("/mydata/file-1.txt"));

//read data from ReusableBufferedInputStream

reusableBufferedInputStream.close();

ReusableBufferedInputStream代码

这是上述ReusableBufferedInputStream的代码。注意,该实现仅覆盖其扩展的InputStream类的read方法。其余的InputStream方法已被省略,以使代码更短,但是我们可以自己实现它们,以防需要。

import java.io.IOException;
import java.io.InputStream;

public class ReusableBufferedInputStream extends InputStream {

    private byte[]      buffer = null;
    private int         writeIndex = 0;
    private int         readIndex  = 0;
    private InputStream source = null;

    public ReusableBufferedInputStream(byte[] buffer) {
        this.buffer = buffer;
    }

    public ReusableBufferedInputStream setSource(InputStream source){
        this.source = source;
        this.writeIndex = 0;
        this.readIndex  = 0;
        return this;
    }

    @Override
    public int read() throws IOException {

        if(readIndex == writeIndex) {
            if(writeIndex == buffer.length) {
                writeIndex = 0;
                readIndex  = 0;
            }
            //data should be read into buffer.
            int bytesRead = readBytesIntoBuffer();
            while(bytesRead == 0) {
                //continue until you actually get some bytes !
                bytesRead = readBytesIntoBuffer();
            }

            //if no more data could be read in, return -1;
            if(bytesRead == -1) {
                return -1;
            }
        }

        return 255 & this.buffer[readIndex++];
    }

    private int readBytesIntoBuffer() throws IOException {
        int bytesRead = this.source.read(this.buffer, this.writeIndex, this.buffer.length - this.writeIndex);
        writeIndex += bytesRead;
        return bytesRead;
    }

    @Override
    public void close() throws IOException {
        this.source.close();
    }
}