当前位置:   article > 正文

BufferedInputStream如何缓冲IO以及InputStream中的read(byte[] b) 是否具有缓冲功能_inputstream缓冲

inputstream缓冲

InputStream本身提供了3个read方法,其中2个是将字节码读到一个byte数组中。这2个方法是不是和BuffereInputStream一样,提供了缓冲功能?

带着这个疑问,分析了下源码。

首先,从顶级的InputStream开始:

  1. read();
  2. read(byte[]);
  3. read(byte[],int off,int len);

第2个read(byte[])其实就是read(b, 0, b.length) ,所以等同于第3个。

第1个read()方法,api介绍如下: 

从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值,该值即是读取的字节值。子类必须提供此方法的一个实现。
如果已经到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。 

第3个read(byte[], int off, int len)方法,api介绍如下: 

将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值,以整数形式返回实际读取的字节数。 
在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。 
如果 len 为 0,则不读取任何字节并返回 0;否则,尝试读取至少1个字节。如果流位于文件末尾而没有可用的字节,则返回值 -1;否则,至少读取1个字节,并将其存储在 b 中。 
将读取的第1个字节存储在元素 b[off] 中,下一个存储在 b[off+1] 中,依次类推。读取的字节数最多等于 len。设 k 为实际读取的字节数,这些字节将存储在 b[off] 到 b[off+k-1] 的元素中,不影响 b[off+k] 到 b[off+len-1] 的元素。 
在任何情况下,b[0] 到 b[off] 的元素以及 b[off+len] 到 b[b.length-1] 的元素都不会受到影响。 
类 InputStream 的 read(b, off, len) 方法重复调用方法 read()。如果第1次这样的调用导致 IOException,则从对 read(b, off, len) 方法的调用中返回该异常。如果对 read() 的任何后续调用导致 IOException,则捕获该异常并将其视为到达文件末尾;到达该点时读取的字节存储在 b 中,并返回发生异常之前读取的字节数。在已读取输入数据 len 的请求数量、检测到文件结束标记、抛出异常前,此方法的默认实现将一直阻塞。 建议子类提供此方法更为有效的实现

注意:

第1点:InputStream是所有输入流的顶级类,只定义,不实现,具体的由子类去实现,如AudioInputStream、 ByteArrayInputStream、FileInputStream等。 
第2点:指明了InputStream的read(byte[],int off,int len)的实现方式,就是简单的调用read()方法而已,而read()方法是一次只读取一个字节,依然每次都要调用底层系统,所以InputStream的read(byte[],int off,int len)性能和直接调用read()一样,byte[]缓冲区在这是摆设。 
第3点:由于第2点InputStream的read(byte[],int off,int len)是重复调用read()方法,byte[]缓冲区只是摆设,才建议子类提供性能更好的方式来覆盖read(byte[],int off,int len)方法。 

InputStream的子类有很多,这里只拿FileInputStream举例。 
下面是FileInputStream的部分源码: 

  1. public native int read() throws IOException;
  2. private native int readBytes(byte b[], int off, int len) throws IOException;
  3. public int read(byte b[]) throws IOException {
  4. return readBytes(b, 0, b.length);
  5. }
  6. public int read(byte b[], int off, int len) throws IOException {
  7. return readBytes(b, off, len);
  8. }

这里两个read方法都是用本地方法实现。因为FileInputStream是跟底层操作系统直接交互的,没有比用本地方法来实现的性能更好、更容易的了。FileInputStream采用了第3点里的建议,真正实现了缓存功能。 

既然FileInputStream已经实现了缓存来提高性能,那么BufferedInputStream又用来干嘛? 
先看api介绍: 

BufferedInputStream 为另一个输入流添加一些功能,即:缓冲输入,以及支持 mark 和 reset 方法的能力。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。mark 操作记录输入流中的某个点,reset 操作使得“在从输入流中获取新字节之前,再次读取自最后一次 mark 操作后读取的所有字节”。

其实,上面所说的“缓冲输入”并不是像FileInputStream那样用本地方法来提高性能,而是指:在这基础上,为了程序员操作方便,内部提供了一个缓冲区(byte[]类型的buf,默认为8192字节),并装饰了FileInputStream类(构造BufferedInputStram时,必须提供被装饰的InputStream)。 
使用FileInputStream时,read()是从底层读一个字节,read(byte[], int off,i nt len) 则是一次性读取了 len-off 个字节,我们需提供一个byte[]来存放。
使用BufferedInputStream时,其read()其实和read(byte[], int off, int len)一样,内部都是调用“构造输入的FileInputStream的read(byte[], int off,i nt len)方法”,将底层数据读入到byte[]里,而byte[]不需要我们来提供,类本身定义了一个byte[] buf数组来存放这些数据。

如果在使用BufferedInputStream时,我们的程序不需要对byte[]数组操作,直接这样写就行了: 

  1. FileInputStream fis = new FileInputStream("d:\\a.txt");
  2. BufferedInputStream bis = new BufferedInputStream(fis);
  3. int data = 0;
  4. while((data=bis.read()) != -1) {
  5. //......
  6. }

这样,虽然也是一次读一个字节,但不是每次都从底层读取数据,而是一次调用底层系统读取了最多buf.length个字节到buf数组中,然后从 buf 中 一次读一个字节,减少了频繁调用底层接口的开销。 

等同于以下代码:

  1. FileInputStream fis = new FileInputStream("d:\\a.txt");
  2. byte[] mybuff = new byte[1024];
  3. int count=0;
  4. while ((count=fis.read(mybuff)) != -1) {
  5. //......
  6. }

如果使用BufferedInputStream的read(byte[], int off, int len),那么缓冲区则由传入的byte[]来充当(虽然内部还是使用buf,但buf其实为传入的byte[]数组)。 

如果要缓冲数据,那么该选择FileInputStream,还是BufferedInputStream?BufferedInputStream主要不是提供buf,而是封装了缓冲和标记/回读的功能。如果我们既不用到标记/回读功能,又不要操作中间的缓冲数组,那么显而易见,直接使用FileInputStream的read(byte[],int off,int len)是效率最高的。 

为什么使用缓冲时性能更好?因为应用程序可以将多个字节写入底层输出流中(native read(byte)),而不必针对每个字节写入都调用底层系统(native read())。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/146855
推荐阅读
相关标签
  

闽ICP备14008679号