【Java8源码分析】IO包-InputStream、FileInputStream和BufferedInputStream总结

来源:互联网 发布:淘宝上传没生产许可证 编辑:程序博客网 时间:2024/05/22 13:27

转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72875258


Java的IO类型可分为两大类:

  • 面向字符流:Reader & Writer
  • 面向字节流:InputStream & OutputStream

本文主要从源码方面分析一下常用的面向字节流的输入流

  • InputStream:所有输入流的抽象父类,定义了常用输入函数
  • FileInputStream:针对文件的输入流
  • BufferedInputStream:带有缓冲区的高效输入流

1 InputStream源码解析

public abstract class InputStream implements Closeable {    private static final int MAX_SKIP_BUFFER_SIZE = 2048;    // 读取下一个字节,返回0-255,读取失败返回-1,read()会阻塞直至读取成功或结束    public abstract int read() throws IOException;    // 把字节流读入到传入的字节数组    public int read(byte b[]) throws IOException {        return read(b, 0, b.length);    }    // 把字节读到字节数组指定位置    public int read(byte b[], int off, int len) throws IOException {        if (b == null) {            throw new NullPointerException();        } else if (off < 0 || len < 0 || len > b.length - off) {            throw new IndexOutOfBoundsException();        } else if (len == 0) {            return 0;        }        int c = read();        if (c == -1) {            return -1;        }        b[off] = (byte)c;        int i = 1;        try {            // 循环调用read,一个字节一个字节读,有可能效率非常低            for (; i < len ; i++) {                c = read();                if (c == -1) {                    break;                }                b[off + i] = (byte)c;            }        } catch (IOException ee) {        }        return i;    }}

InputStream默认的read(byte[])方法是通过多次调用read()函数一个字节一个字节读到字节数组中。对于文件读取这种,read()函数都要进行一次磁盘IO,所以会导致性能低下。


2 FileInputStream源码解析

public class FileInputStream extends InputStream{    private native int read0() throws IOException;    private native int readBytes(byte b[], int off, int len) throws IOException;    public int read() throws IOException {        return read0();    }    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);    }}

FileInputStream中的read()read(byte[])方法都是navtice方法。

InputStream类实现的read是即时读取的,即每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),可想而知,如果数据量巨大,这样的磁盘消耗非常可怕。

InputStream类的read(byte[])方法则是一次读取多个字节到buffer中。


3 BufferedInputStream源码解析

3.1 属性域

public class BufferedInputStream extends FilterInputStream {    // 存储的输入流对象    protected volatile InputStream in;    // 缓冲区大小默认未8MB    private static int DEFAULT_BUFFER_SIZE = 8192;    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;    // 缓冲区    protected volatile byte buf[];    // 缓冲区中有效数据容量    protected int count;    // 缓冲区读到的当前位置    protected int pos;    // 构造函数,默认缓冲区大小    public BufferedInputStream(InputStream in) {        this(in, DEFAULT_BUFFER_SIZE);    }    // 构造函数,设置缓冲区大小    public BufferedInputStream(InputStream in, int size) {        super(in);        if (size <= 0) {            throw new IllegalArgumentException("Buffer size <= 0");        }        buf = new byte[size];    } }

3.2 read()函数

public synchronized int read() throws IOException {    // 如果缓冲区已经读完,则调用fill填充缓冲区    if (pos >= count) {        fill();        if (pos >= count)            return -1;    }    return getBufIfOpen()[pos++] & 0xff;}//填充字节到缓冲区中,假设缓冲区中的数据已经全部读完private void fill() throws IOException {    byte[] buffer = getBufIfOpen();    // ...    // 前面省略了缓冲区扩容、缓冲区mark标记处理等代码    count = pos;    // 一次性读取多个字节到缓冲区中    int n = getInIfOpen().read(buffer, pos, buffer.length - pos);    if (n > 0)        count = n + pos;}

BufferedInputStream改进了read()方法,它会先从自身的缓冲区中取字节,当缓冲区的字节取完后,会调用fill()函数把缓冲取一次性填充满,再取字节。


4 总结

(1)不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。

(2)带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多。

(3)有一种情况下,FileInputStream和BufferedInputStream的效率相差不大:即每次读取数据量接近或远超BufferedInputStream的缓冲区大小时(默认8MB),两者效率就没有明显差别了。


转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72875258

阅读全文
0 1