先介绍一下为啥比较他俩,因为BufferedInputStream是FileInputStream 的装饰者,但是我发现FileInputStream 中也是有缓存的,所以我就好奇装饰者起什么作用呢,这就是我写这篇文章的原因。
先说结论,当缓冲区的大小比8192小的时候,BufferedInputStream的效率更好。
发现是BufferedInputStream有一个默认为8192字节的缓冲区,当自定义的缓冲区小于8192的时候,默认一次性从硬盘中读取8192字节到内存中,然后每次只按自定义的缓冲量返回数据,性能好在了减少了读取硬盘的次数。
BufferedInputStream的示意图
FileInputStream 示意图
示例
public class FileOperator {
/** buffer size in bytes */
final static int BUFFER_SIZE = 100;
/**
* copy file using FileInputStream & FileOutputStream
* @param src copy from
* @param dest copy to
* @return;
*/
public static void copyWithFileStream(File src, File dest){
FileInputStream input = null;
FileOutputStream output = null;
try {
input = new FileInputStream(src);
output = new FileOutputStream(dest);
byte[] buffer = new byte[BUFFER_SIZE];
int copySize;
while ((copySize = input.read(buffer)) > 0){
output.write(buffer, 0, copySize);
output.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
input.close();
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* copy file using BufferedInputStream & BufferedOutputStream
* @param src copy from file
* @param dest copy to file
* @return;
*/
public static void copyWithBufferedStream(File src, File dest){
BufferedInputStream bufferedInput = null;
BufferedOutputStream bufferedOutput = null;
try {
bufferedInput = new BufferedInputStream(new FileInputStream(src));
bufferedOutput = new BufferedOutputStream(new FileOutputStream(dest));
byte[] buffer = new byte[BUFFER_SIZE];
int copySize;
while ((copySize = bufferedInput.read(buffer)) > 0){
bufferedOutput.write(buffer, 0, copySize);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedInput.close();
bufferedOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class FileOperatorTest{
public static void main(String args[]){
File src = new File("test.txt");
File dest = new File("copyTest.txt");
try {
if (!dest.exists()){
dest.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
//test copy using FileStream
int startTime = System.currentTimeMillis();
FileOperator.copyWithFileStream(src, dest);
int endTime = System.currentTimeMillis();
System.out.println("Copy file using FileStream takes : " + (endTime - startTime) + " ms.");
//test copy using BufferedStream
startTime = System.currentTimeMillis();
FileOperator.copyWithBufferedStream(src, dest);
endTime = System.currentTimeMillis();
System.out.println("Copy file using BufferedStream takes : " + (endTime - startTime) + " ms.");
}
}
【运行结果】
测试文件大小约为900M,以下是在设定BUFFER_SIZE为不同值时的一次执行结果:
BUFFER_SIZE = 100
Copy file using FileStream takes: 42680 ms.
Copy file using BufferedStream takes: 2407 ms.
BUFFER_SIZE = 8192
Copy file using FileStream takes: 1689 ms.
Copy file using BufferedStream takes: 1654 ms.
BUFFER_SIZE = 1000000
Copy file using FileStream takes: 957 ms.
Copy file using BufferedStream takes: 929 ms.
关键源码分析如下
BufferedInputStream的源码
public synchronized int read(byte b[], int off, int len)
throws IOException
{
getBufIfOpen(); // Check for closed stream
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int n = 0;
for (;;) {
int nread = read1(b, off + n, len - n);
if (nread <= 0)
return (n == 0) ? nread : n;
n += nread;
if (n >= len)
return n;
// if not closed but no bytes available, return
InputStream input = in;
if (input != null && input.available() <= 0)
return n;
}
}
getBufIfOpen方法中的buf是8192,所以buffer =8192。其中类实例化的时候,buf为默认长度
private byte[] getBufIfOpen() throws IOException {
byte[] buffer = buf;
if (buffer == null)
throw new IOException("Stream closed");
return buffer;
}
//构造函数设置默认长度
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
默认长度
private static int DEFAULT_BUFFER_SIZE = 8192;
read1方法是重点
private int read1(byte[] b, int off, int len) throws IOException {
int avail = count - pos;
if (avail <= 0) {
/* If the requested length is at least as large as the buffer, and
if there is no mark/reset activity, do not bother to copy the
bytes into the local buffer. In this way buffered streams will
cascade harmlessly. */
if (len >= getBufIfOpen().length && markpos < 0) {
return getInIfOpen().read(b, off, len);
}
fill();
avail = count - pos;
if (avail <= 0) return -1;
}
int cnt = (avail < len) ? avail : len;
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
pos += cnt;
return cnt;
}
count=0,pos=0 所以avail =0
当自定义的长度为100时,所以len=100 len >= getBufIfOpen().length(8192)的条件是不满足的,所以不进入结构体,接下来调用fill方法
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else if (buffer.length >= MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
} else { /* grow buffer */
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
// Can't replace buf if there was an async close.
// Note: This would need to be changed if fill()
// is ever made accessible to multiple threads.
// But for now, the only way CAS can fail is via close.
// assert buf == null;
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
其中markpos =-1,所以pos=0
pos >= buffer.length的条件是不满足的,所以执行到count=pos=0
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
private InputStream getInIfOpen() throws IOException {
InputStream input = in;
if (input == null)
throw new IOException("Stream closed");
return input;
}
这个就是调用了inputsteram的read方法,就是按缓存量为8192的进行读取的
buffer有了数据,由于是同一个引用,所以buf缓存中也有了数据
n=8192,count=8192
回到read1方法 avail = count - pos;
avail=8192,接下来的三元运算 cnt=100
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
其中的getBufIfOpen()就是取buf字节数组,就是有了数据的8192的字节数组,就是将buf的前100个字节复制到b中就是自定义的字节数组中
然后pos按len长度自增,返回cnt=100
返回到read方法就不解释了,挺简单了
上面例子搬运自下面的大佬