赞
踩
InputStream本身提供了3个read方法,其中2个是将字节码读到一个byte数组中。这2个方法是不是和BuffereInputStream一样,提供了缓冲功能?
带着这个疑问,分析了下源码。
首先,从顶级的InputStream开始:
- read();
- read(byte[]);
- 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的部分源码:
- public native int read() throws IOException;
-
- private native int readBytes(byte b[], int off, int len) throws IOException;
-
- public int read(byte b[]) throws IOException {
- return readBytes(b, 0, b.length);
- }
-
- public int read(byte b[], int off, int len) throws IOException {
- return readBytes(b, off, len);
- }
这里两个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[]数组操作,直接这样写就行了:
- FileInputStream fis = new FileInputStream("d:\\a.txt");
- BufferedInputStream bis = new BufferedInputStream(fis);
- int data = 0;
- while((data=bis.read()) != -1) {
- //......
- }
这样,虽然也是一次读一个字节,但不是每次都从底层读取数据,而是一次调用底层系统读取了最多buf.length个字节到buf数组中,然后从 buf 中 一次读一个字节,减少了频繁调用底层接口的开销。
等同于以下代码:
- FileInputStream fis = new FileInputStream("d:\\a.txt");
- byte[] mybuff = new byte[1024];
- int count=0;
- while ((count=fis.read(mybuff)) != -1) {
- //......
- }
如果使用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())。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。