Java IO(4)

转载:http://www.cnblogs.com/zhaoyanjun/p/6376937.html
   http://blog.csdn.net/dabing69221/article/details/16996877

InputStream
|__FilterInputStream
        |__BufferedInputStream

首先抛出一个问题,有了InputStream为什么还要有BufferedInputStream?

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

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

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

现在就可以回答在本文的开头提出的问题:

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

总结:
BufferedInputStream 是缓冲输入流。它继承于FilterInputStream。

BufferedInputStream 的作用是为另一个输入流添加一些功能,例如,提供“缓冲功能”以及支持mark()标记和reset()重置方法。

BufferedInputStream 本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。

BufferedInputStream API简介
源码关键字段分析

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

构造函数

BufferedInputStream(InputStream in) //使用默认buf大小、底层字节输入流构建bis 

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

一般方法介绍

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

BufferedOutputStream API简介
关键字段

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

构造函数

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

构造函数源码:

/**
  * Creates a new buffered output stream to write data to the
  * specified underlying output stream.
  * @param   out   the underlying output stream.
  */
 public BufferedOutputStream(OutputStream out) {
     this(out, 8192);
 }

 /**
  * Creates a new buffered output stream to write data to the
  * specified underlying output stream with the specified buffer
  * size.
  *
  * @param   out    the underlying output stream.
  * @param   size   the buffer size.
  * @exception IllegalArgumentException if size <= 0.
  */
 public BufferedOutputStream(OutputStream out, int size) {
     super(out);
     if (size <= 0) {
         throw new IllegalArgumentException("Buffer size <= 0");
     }
     buf = new byte[size];
 }

一般方法

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

那么什么时候flush()才有效呢?
答案是:当OutputStream是BufferedOutputStream时。

当写文件需要flush()的效果时,需要
FileOutputStream fos = new FileOutputStream("c:\a.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
也就是说,需要将FileOutputStream作为BufferedOutputStream构造函数的参数传入,然后对BufferedOutputStream进行写入操作,才能利用缓冲及flush()。

查看BufferedOutputStream的源代码,发现所谓的buffer其实就是一个byte[]。
BufferedOutputStream的每一次write其实是将内容写入byte[],当buffer容量到达上限时,会触发真正的磁盘写入。
而另一种触发磁盘写入的办法就是调用flush()了。

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

实战演练1:复制文件.
操作:使用缓存流将F盘根目录里面名字为:123.png 图片复制成 abc.png

package com.app;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;


public class A3 {

    public static void main(String[] args) throws IOException {

        String filePath = "F:/123.png" ;
        String filePath2 = "F:/abc.png" ;
        File file = new File( filePath ) ;
        File file2 = new File( filePath2 ) ;
        copyFile( file , file2 );

    }
    
    /**
     * 复制文件
     * @param oldFile
     * @param newFile
     */
    public static void copyFile( File oldFile , File newFile){
        InputStream inputStream = null ;
        BufferedInputStream bufferedInputStream = null ;

        OutputStream outputStream = null ;
        BufferedOutputStream bufferedOutputStream = null ;

        try {
            inputStream = new FileInputStream( oldFile ) ;
            bufferedInputStream = new BufferedInputStream( inputStream ) ;

            outputStream = new FileOutputStream( newFile ) ;
            bufferedOutputStream = new BufferedOutputStream( outputStream ) ;

            byte[] b=new byte[1024];   //代表一次最多读取1KB的内容

            int length = 0 ; //代表实际读取的字节数
            while( (length = bufferedInputStream.read( b ) )!= -1 ){
                //length 代表实际读取的字节数
                bufferedOutputStream.write(b, 0, length );
            }
            //缓冲区的内容写入到文件
            bufferedOutputStream.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e) {
            e.printStackTrace();
        }finally {

            if( bufferedOutputStream != null ){
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( bufferedInputStream != null){
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            if( inputStream != null ){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            if ( outputStream != null ) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

如何正确的关闭流

在上面的代码中,我们关闭流的代码是这样写的。

finally {

            if( bufferedOutputStream != null ){
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( bufferedInputStream != null){
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( inputStream != null ){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if ( outputStream != null ) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

思考:在处理流关闭完成后,我们还需要关闭节点流吗?

让我们带着问题去看源码:

bufferedOutputStream.close();
 /**
 * Closes this input stream and releases any system resources
 * associated with the stream.
 * Once the stream has been closed, further read(), available(), reset(),
 * or skip() invocations will throw an IOException.
 * Closing a previously closed stream has no effect.
 *
 * @exception  IOException  if an I/O error occurs.
 */
public void close() throws IOException {
    byte[] buffer;
    while ( (buffer = buf) != null) {
        if (bufUpdater.compareAndSet(this, buffer, null)) {
            InputStream input = in;
            in = null;
            if (input != null)
                input.close();
            return;
        }
        // Else retry in case a new buf was CASed in fill()
    }
}

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

那么如果我们想逐个关闭流,我们该怎么做?

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

看懂了怎么正确的关闭流之后,那么我们就可以优化上面的代码了,只关闭外层的处理流。

finally {

            if( bufferedOutputStream != null ){
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( bufferedInputStream != null){
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
为什么要flush

与在网络硬件中缓存一样,流还可以在软件中得到缓存,即直接在Java代码中缓存。这可以通过BufferedOutputStream或BufferedWriter 链接到底层流上来实现。因此,在写完数据时,flush就显得尤为重要。比如:


上图中WEB服务器通过输出流向客户端响应了一个300字节的信息,但是,这时的输出流有一个1024字节的缓冲区。所以,输出流就一直等着WEB服务器继续向客户端响应信 息,当WEB服务器的响应信息把输出流中的缓冲区填满时,这时,输出流才向WEB客户端响应消息。
为了解决这种尴尬的局面,flush()方法出现了。flush()方法可以强迫输出流(或缓冲的流)发送数据,即使此时缓冲区还没有填满,以此来打破这种死锁的状态。
当我们使用输出流发送数据时,当数据不能填满输出流的缓冲区时,这时,数据就会被存储在输出流的缓冲区中。如果,我们这个时候调用关闭(close)输出流,存储在输出流的缓冲区中的数据就会丢失。所以说,关闭(close)输出流时,应先刷新(flush)换冲的输出流,话句话说就是:“迫使所有缓冲的输出数据被写出到底层输出流中”。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • # 3.1 File # ## 3.1.1 File基本概念 ## 1.基本概念 -File类用于表示文件(目录)...
    闫子扬阅读 452评论 0 0
  • 1 IO(二)No19 【 缓冲流:内置了缓冲区,对现有的流对象进行了封装,实现了高效的读写操作并增强了功能 ...
    征程_Journey阅读 703评论 0 1
  • 如果想你了, 我会掏出手机,看看有没有你的微信, 即使我知道,机率是那么的渺茫。 如果想你了, 我会用拇指在手机上...
    靜寂夜雨阅读 196评论 0 0
  • 昨天无意中看到你的作文《妈妈,我想对您说》,看了以后心里满不是滋味的。原来我己经悄悄地“伤害”着你了,虽然我一直都...
    懒散的我阅读 335评论 0 0
  • 杨绛说:你的问题是想得太多,而看的太少。这一直激励着我坚持看书,努力提高自己的思想水平。也让自己变得想法多变,逻辑...
    方鸿渐加油努力奋斗阅读 1,152评论 2 1