通过分析FileInputStream类和BufferedInputStream类中的部分核心代码来理解带缓冲的字节输入流的实现原理,缓冲输出流原理与之相同,在此不再赘述。
FileI nputStream 源码
package java.io; public class FileInputStream extends InputStream{ private native int read0() throws IOException; public int read() throws IOException { Object traceContext = IoTrace.fileReadBegin(path); int b = 0 ; try { b = read0(); } finally { IoTrace.fileReadEnd(traceContext, b == -1 ? 0 : 1 ); } return b; } private native int readBytes( byte b[], int off, int len) throws IOException; public int read( byte b[]) throws IOException { Object traceContext = IoTrace.fileReadBegin(path); int bytesRead = 0 ; try { bytesRead = readBytes(b, 0 , b.length); } finally { IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead); } return bytesRead; } public int read( byte b[], int off, int len) throws IOException { Object traceContext = IoTrace.fileReadBegin(path); int bytesRead = 0 ; try { bytesRead = readBytes(b, off, len); } finally { IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead); } return bytesRead; } }
通过源码可以看到,如果用read()方法读取一个文件,每读取一个字节就要访问一次硬盘,这种读取的方式效率是很低的。即便使用read(byte b[])方法一次读取多个字节,当读取的文件较大时,也会频繁的对磁盘操作。
为了提高字节输入流的工作效率,Java 提供了BufferedInputStream类。
首先解释一下BufferedInputStream的基本原理:
API 文档的解释:在 创建 BufferedInputStream时,会创建一个内部缓冲区数组。在读取流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。
也就是说,Buffered类初始化时会创建一个较大的byte数组,一次性从底层输入流中读取多个字节来填充byte数组,当程序读取一个或多个字节时,可直接从byte数组中获取,当内存中的byte读取完后,会再次用底层输入流填充缓冲区数组。
这种从直接内存中读取数据的方式要比每次都访问磁盘的效率高很多。下面通过分析源码,进一步理解其原理:
BufferedInputStream源码
package java.io; public class BufferedInputStream extends FilterInputStream { private static int defaultBufferSize = 8192 ; protected volatile byte buf[]; protected int count; protected int pos; private InputStream getInIfOpen() throws IOException { InputStream input = in; if (input == null ) throw new IOException( "Stream closed" ); return input; } private byte [] getBufIfOpen() throws IOException { byte [] buffer = buf; if (buffer == null ) throw new IOException( "Stream closed" ); return buffer; } public BufferedInputStream(InputStream in) { this (in, defaultBufferSize); } public BufferedInputStream(InputStream in, int size) { super (in); if (size <= 0 ) { throw new IllegalArgumentException( "Buffer size <= 0" ); } buf = new byte [size]; } private void fill() throws IOException { byte [] buffer = getBufIfOpen(); if (markpos < 0 ) pos = 0 ; count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0 ) count = n + pos; } public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return - 1 ; } return getBufIfOpen()[pos++] & 0xff ; } private int read1( byte [] b, int off, int len) throws IOException { int avail = count - pos; if (avail <= 0 ) { 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; } public synchronized int read( byte b[], int off, int len){ } }
最后我们通过一个实例更直观的说明BufferedStream的高效率。
import java.io.*; public class CopyMp3 { private static File file = new File( "D:\\1.mp4" ); private static File file_cp = new File( "D:\\1_cp.mp4" ); public void copy() throws IOException { FileInputStream in = new FileInputStream(file); FileOutputStream out = new FileOutputStream(file_cp); byte [] buf = new byte [ 1024 ]; int len = 0 ; while ((len = in.read(buf)) != - 1 ) { out.write(buf); } in.close(); out.close(); } public void copyByBuffer() throws IOException { BufferedInputStream in = new BufferedInputStream( new FileInputStream(file)); BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream(file_cp)); byte [] buf = new byte [ 1024 ]; int len; while ((len = in.read(buf)) != - 1 ) { out.write(buf); } in.close(); out.close(); } public static void main(String[] args) throws IOException { CopyMp3 copy=new CopyMp3(); long time1=System.currentTimeMillis(); copy.copy(); long time2=System.currentTimeMillis(); System.out.println("直接复制用时:" +(time2-time1)+ "毫秒" ); long time3=System.currentTimeMillis(); copy.copyByBuffer(); long time4=System.currentTimeMillis(); System.out.println("缓冲区复制用时:" +(time4-time3)+ "毫秒" ); } }
当复制一段 379M 的视频文件时:
直接复制用时: 3155 毫秒
缓冲区复制用时: 865 毫秒
通过实验可以看出 带缓冲区的数据流效率远远高于普通的数据流,而且操作的文件越大,优势越明显。
BufferedInputStream设计模式说明:
这里用到了装设者设计模式, BufferedInputStream 为装饰者类, FileInputStream 为被装饰者类,前者的作用就是为了加强后者已有的功能,这里就是为了提高数据流的读写效率。
BufferedInputStream的构造方法定义:public BufferedInputStream(InputStream in)可以看出,Buffered可以装饰任何一个InputSteam的 子类。
【如有理解错误的地方,还望大神们指正,谢谢。】
关于read方法读取内容
1.没有markpos的情况很简单:
2有mark的 情况比较复杂