概览

缓冲流-BufferedInputStream和BufferedOutputStream
继承关系如下:

  1. InputStream
  2. |__FilterInputStream
  3. |__BufferedInputStream

BufferedInputStream和BufferedOutputStream这两个类分别是FilterInputStream和FilterOutputStream的子类,作为装饰器子类,使用它们可以防止每次读取/发送数据时进行实际的写操作,代表着使用缓冲区。

为什么需要缓冲操作

我们有必要知道不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!

同时正因为它们实现了缓冲功能,所以要注意在使用BufferedOutputStream写完数据后,要调用flush()方法或close()方法,强行将缓冲区中的数据写出。否则可能无法写出数据。与之相似还BufferedReader和BufferedWriter两个类。

flush()和close() 的区别:

  • flush()方法:用来刷新缓冲区的,会先将内存缓冲区的字节刷新到文件里面,刷新后流还存在,可以接着使用。
  • close()方法:用来关闭流释放资源的,如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后流不存在了。

所以结论就是:BufferedInputStream和BufferedOutputStream类就是实现了缓冲功能的输入流/输出流。使用带缓冲的输入输出流,效率更高,速度更快。

源码

BufferedInputStream

1、关键字段

  1. private static int defaultBufferSize = 8192;//内置缓存字节数组的大小 8KB
  2. protected volatile byte buf[]; //内置缓存字节数组
  3. protected int count; //当前buf中的字节总数、注意不是底层字节输入流的源中字节总数
  4. protected int pos; //当前buf中下一个被读取的字节下标
  5. protected int markpos = -1; //最后一次调用mark(int readLimit)方法记录的buf中下一个被读取的字节的位置
  6. protected int marklimit; //调用mark后、在后续调用reset()方法失败之前云寻的从in中读取的最大数据量、用于限制被标记后buffer的最大值

2、构造函数

  1. BufferedInputStream(InputStream in) //使用默认buf大小、底层字节输入流构建bis
  2. BufferedInputStream(InputStream in, int size) //使用指定buf大小、底层字节输入流构建bis

3、常用方法

  1. int available(); //返回底层流对应的源中有效可供读取的字节数
  2. void close(); //关闭此流、释放与此流有关的所有资源
  3. boolean markSupport(); //查看此流是否支持mark
  4. void mark(int readLimit); //标记当前buf中读取下一个字节的下标
  5. int read(); //读取buf中下一个字节
  6. int read(byte[] b, int off, int len); //读取buf中下一个字节
  7. void reset(); //重置最后一次调用mark标记的buf中的位子
  8. long skip(long n); //跳过n个字节、 不仅仅是buf中的有效字节、也包括in的源中的字节

BUfferedOutputStream

1、关键字段

  1. protected byte[] buf; //内置缓存字节数组、用于存放程序要写入out的字节
  2. protected int count; //内置缓存字节数组中现有字节总数

2、构造函数

  1. BufferedOutputStream(OutputStream out); //使用默认大小、底层字节输出流构造bos。默认缓冲大小是 8192 字节( 8KB )
  2. BufferedOutputStream(OutputStream out, int size); //使用指定大小、底层字节输出流构造bos

3、一般方法

  1. //在这里提一句,`BufferedOutputStream`没有自己的`close`方法,当他调用父类`FilterOutputStrem`的方法关闭时,会间接调用自己实现的`flush`方法将buf中残存的字节flush到out中,再`out.flush()`到目的地中,DataOutputStream也是如此。
  2. void flush(); 将写入bos中的数据flushout指定的目的地中、注意这里不是flushout中、因为其内部又调用了out.flush()
  3. write(byte b); 将一个字节写入到buf
  4. write(byte[] b, int off, int len); b的一部分写入buf

注意:1.BufferedOutputStream在close()时会自动flush。2.BufferedOutputStream在不调用close()的情况下,缓冲区不满,又需要把缓冲区的内容写入到文件或通过网络发送到别的机器时,才需要调用flush.

实际示例

将F盘的123.jpg文件复制成F盘的abc图片

  1. public class Buffered {
  2. public static void main(String[] args) {
  3. String filePath = "F:/123.jpg" ;
  4. String filePath2 = "F:/abc.jpg" ;
  5. File file = new File( filePath ) ;
  6. File file2 = new File( filePath2 ) ;
  7. copyFile( file , file2 );
  8. }
  9. private static void copyFile(File oldFile, File newFile) {
  10. InputStream inputStream = null ;
  11. BufferedInputStream bufferedInputStream = null ;
  12. OutputStream outputStream = null ;
  13. BufferedOutputStream bufferedOutputStream = null ;
  14. try {
  15. inputStream = new FileInputStream( oldFile ) ;
  16. //BufferedInputStream 的构造函数中的参数要求是InputStream
  17. bufferedInputStream = new BufferedInputStream( inputStream ) ;
  18. outputStream = new FileOutputStream( newFile ) ;
  19. bufferedOutputStream = new BufferedOutputStream( outputStream ) ;
  20. byte[] b=new byte[1024]; //代表一次最多读取1KB的内容
  21. int length = 0 ; //代表实际读取的字节数
  22. while( (length = bufferedInputStream.read( b ) )!= -1 ){
  23. //length 代表实际读取的字节数
  24. bufferedOutputStream.write(b, 0, length );
  25. }
  26. //缓冲区的内容写入到文件
  27. bufferedOutputStream.flush();
  28. } catch (FileNotFoundException e) {
  29. e.printStackTrace();
  30. }catch (IOException e) {
  31. e.printStackTrace();
  32. }finally {
  33. //finally 处理,需要关闭流
  34. if( bufferedOutputStream != null ){
  35. try {
  36. bufferedOutputStream.close();
  37. } catch (IOException e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. if( bufferedInputStream != null){
  42. try {
  43. bufferedInputStream.close();
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. if( inputStream != null ){
  49. try {
  50. inputStream.close();
  51. } catch (IOException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. if ( outputStream != null ) {
  56. try {
  57. outputStream.close();
  58. } catch (IOException e) {
  59. e.printStackTrace();
  60. }
  61. }
  62. }
  63. }
  64. }

流关闭问题

BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。因此,可以只调用外层流的close方法关闭其装饰的内层流。

那么如果我们想逐个关闭流,我们该怎么做?
答案是:先关闭外层流,再关闭内层流。一般情况下是:先打开的后关闭,后打开的先关闭;另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b

所以,上述finally块代码可更改为:

  1. finally {
  2. if( bufferedOutputStream != null ){
  3. try {
  4. bufferedOutputStream.close();
  5. } catch (IOException e) {
  6. e.printStackTrace();
  7. }
  8. }
  9. if( bufferedInputStream != null){
  10. try {
  11. bufferedInputStream.close();
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. }