转载请注明出处: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