Java 缓冲流简介及简单用法

在java编程中, 我们有时会听到缓冲流和原始流等字眼.


其实在之前的博文中, 提到过流可以分为原始流和处理流.

http://blog.csdn.net/nvd11/article/details/30126233

也就是说处理流是包裹在原始流对原始流的数据进行进一步的处理, 这时的流就有两层了.


而缓冲流就是处理流的一种.


一, 缓冲流的定义

缓冲流是处理流的一种, 它依赖于原始的输入输出流, 它令输入输出流具有1个缓冲区, 显著减少与外部设备的IO次数, 而且提供一些额外的方法.


可见, 缓冲流最大的特点就是具有1个缓冲区! 而我们使用缓冲流无非两个目的:

1. 减少IO次数(提升performance)

2. 使用一些缓冲流的额外的方法.



二, 不使用缓冲流的例子

下面例子, 将1个大小为32MB的音乐文件复制到另1个地方. (/home/gateman/Music/Nickelback - Rockstar.flac 复制到 /home/gateman/tmp/Rockstar.flac

而且只是用原始的 FileInputStream 和 FileOutputStream, 1个1个字节的读取写入.

package Stream_kng.BufferStream_kng;

import java.io.*;

public class Stream2{

    public static void f(){
        FileInputStream fi = null;
        try{
            fi = new FileInputStream("/home/gateman/Music/Nickelback - Rockstar.flac");
        }catch(FileNotFoundException e){
            System.out.println("File not found!");
        }

        FileOutputStream fo = null;
        try{
            fo = new FileOutputStream("/home/gateman/tmp/Rockstar.flac");
        }catch(Exception e){
            System.out.println("error in file output stream's creation");
            e.printStackTrace();
        }

        int byt;
        try{
            byt = fi.read();
            while(-1 != byt){
                fo.write(byt);
                byt = fi.read();
            }
        }catch(IOException e){
            e.printStackTrace();
        }

        try{
            fo.flush();
        }catch(IOException e){
            System.out.println("Exception in flush()");
        }finally{
            if (null != fo){
                try{
                    fo.close();
                }catch(IOException e){
                    System.out.println("Exception in fo.close()");
                }
            }
           
            if (null != fi){
                try{
                    fi.close();
                }catch(IOException e){
                    System.out.println("Exception in fi.close()");
                }
            }
        }

        System.out.println("copy done!");
    }
}



我们看看编译后的执行效果:

gateman@TPEOS classes $ ls /home/gateman/tmp/Rockstar.flac
ls: cannot access /home/gateman/tmp/Rockstar.flac: No such file or directory
gateman@TPEOS classes $ time java Enter_1 
copy done!

real	0m57.385s
user	0m9.640s
sys	0m47.528s
gateman@TPEOS classes $ ls -lh /home/gateman/tmp/Rockstar.flac
-rw-rw-r-- 1 gateman gateman 32M Jul  1 22:43 /home/gateman/tmp/Rockstar.flac

可见复制区区1个32m的文件都用了将近1分钟,  不可以接受啊.


原因就是输入流fi 和 输出流都是1个1个字节的读写, 也就是说这个程序对硬盘读了 30000多次, 写了30000多次.

而硬盘IO是整个计算机最慢的动作, 所以我们需要减少外部设备的IO.


方法很简单, 就是增加每一次IO的数据量(缓冲), 自然就是减少了IO次数.


三, 使用缓冲流的例子

上面的代码只有两个流.

输入流fi 和 输出流.

下面修改一下, 增加两个缓冲流, 分别包裹着原始的输入输出流.

package Stream_kng.BufferStream_kng;

import java.io.*;

public class BufferStream1{

    public static void f(){
        FileInputStream fi = null;
        try{
            fi = new FileInputStream("/home/gateman/Music/Nickelback - Rockstar.flac");
        }catch(FileNotFoundException e){
            System.out.println("File not found!");
        }

        FileOutputStream fo = null;
        try{
            fo = new FileOutputStream("/home/gateman/tmp/Rockstar.flac");
        }catch(Exception e){
            System.out.println("error in file output stream's creation");
            e.printStackTrace();
        }

        //bufferStream
        BufferedInputStream bis = new BufferedInputStream(fi,512);
        BufferedOutputStream bos = new BufferedOutputStream(fo,512);
        

        int byt;
        try{
            byt = bis.read();
            while(-1 != byt){
                bos.write(byt);
                byt = bis.read();
            }
        }catch(IOException e){
            e.printStackTrace();
        }

        try{
            bos.flush();
        }catch(IOException e){
            System.out.println("Exception in flush()");
        }finally{
            if (null != bos){
                try{
                    bos.close();
                }catch(IOException e){
                    System.out.println("Exception in bos.close()");
                }
            }
           
            if (null != bis){
                try{
                    bis.close();
                }catch(IOException e){
                    System.out.println("Exception in bis.close()");
                }
            }

        }

        System.out.println("copy done!!");
    }
}

分析上面代码, 

 BufferedInputStream bis = new BufferedInputStream(fi,512);
 BufferedOutputStream bos = new BufferedOutputStream(fo,512);

这两句就建立了两个缓冲流对象, 分别包裹了 原始的输入流fi 和 原始的输出流fo. 并指定缓冲区的大小未512kb


后面就只调用缓冲流的read()和write方法.

代码看起来还是1个1个字节地读写的, 跟第二节例子很类似.


但是执行效果大大不同:

gateman@TPEOS classes $ ls -lh /home/gateman/tmp/Rockstar.flac
ls: cannot access /home/gateman/tmp/Rockstar.flac: No such file or directory
gateman@TPEOS classes $ time java Enter_1 
copy done!!

real	0m1.766s
user	0m1.612s
sys	0m0.168s
gateman@TPEOS classes $ ls -lh /home/gateman/tmp/Rockstar.flac
-rw-rw-r-- 1 gateman gateman 32M Jul  1 23:19 /home/gateman/tmp/Rockstar.flac

可见这次执行了1.766秒, 相比之前的57多秒简直不可以同日而语.


原因就是缓冲输入流会预读到512k(缓冲区大小)字节才发给程序, 缓冲输出流写满缓冲区(512k)才写入外部设备.


如下图:





四, 缓冲流输入流 BufferedInputStream流的常用方法介绍

上面程序使用了两个缓冲流:

BufferedInputStream 和 BufferedOutputStream.

分别对应输入和输出.


其实它们的机制类似.

这里只介绍缓冲输入流的常用方法:


4.1 new BufferedInputStream(InputStream is, int bufferSize)

这个是缓冲输入流最常用的构造方法.

它有两个参数, 第一个就是要包裹的输入流,  其中InputStream是1个抽象类, 实际上我们传送的是其子类(例如上面例子的FileInputStream)的对象, 这里用到多态的知识.

第二个参数也很重要, 就是制定缓冲流缓冲区的初始大小. 单位是kb 上面的例子我们指定为512kb.


注意构造方法并不需要强制捕捉异常.


4.2 int read() throws IOException

读取一个字节放入缓冲区, 用法与InputStream的read()基本一样的.


4.3 int read(byte[] bytArr) throws IOException

读取若干个字节放入字节数组bytArr, 返回实际读取的字节个数, 用法与InputStream的同名同参方法基本一样.


4.4 void mark(int readlimit) 和 void reset() throws IOException

这个方法就是缓冲输入流额外提供的方法.


它的作用就是在当前位置作1个标记,  它允许调用另1个方法reset() 令到流重新定位到这个标记上.

有点类似于oracle 的transation 的savepoint 和 rollback


它有什么意义?

它可以令我们在流中定1个标记, 然后读取标记后的若干数据作判断or处理, 如果符合某一条件可以返回到标记位置重新读取...


它的参数 int readlimit?

意思是, 调用reset()前允许读取的字节个个数,  更通俗地说, 一旦你标记后读取超过了readlimit个字节, 那么就不可再调用reset()了, 会抛异常.


而且, 这些操作都是基于缓冲区内处理的, 所以标记后读取的字节数也不能大于缓冲区大小再调用reset(), 所以 readlimit设置大于缓冲区是没有意义的.


4.5 void close() throws IOException

上面的例子有4个流, 两个原始流, 两个缓冲流, 但是到了代码的最后只关闭了两个缓冲流.


实际上, java中关闭1个处理流, 会自动调用处理流包裹的原始流的close()方法, 也就是说回嵌套地关闭流, 所以只需要关闭缓冲流, 不需关闭原始流, 否则抛异常!



五, 不使用缓冲流的缓冲机制.

实际上很多人都明白, 基本的原始流InputStream 有1个方法

int read(byte[]) 一次性地读取多个字节.

这个byte[] 字节数组实际上也是1个缓冲区.


下面例子:

package Stream_kng.BufferStream_kng;

import java.io.*;

public class Stream3{

    public static void f(){
        FileInputStream fi = null;
        try{
            fi = new FileInputStream("/home/gateman/Music/Nickelback - Rockstar.flac");
        }catch(FileNotFoundException e){
            System.out.println("File not found!");
        }

        FileOutputStream fo = null;
        try{
            fo = new FileOutputStream("/home/gateman/tmp/Rockstar.flac");
        }catch(Exception e){
            System.out.println("error in file output stream's creation");
            e.printStackTrace();
        }

        byte[] byteArr = new byte[512]; //use a buffer instead of a byte
        int len;
        try{
            len = fi.read(byteArr);
            while(-1 != len){
                fo.write(byteArr,0,len);
                len = fi.read(byteArr);
            }
        }catch(IOException e){
            e.printStackTrace();
        }

        try{
            fo.flush();
        }catch(IOException e){
            System.out.println("Exception in flush()");
        }finally{
            if (null != fo){
                try{
                    fo.close();
                }catch(IOException e){
                    System.out.println("Exception in fo.close()");
                }
            }
           
            if (null != fi){
                try{
                    fi.close();
                }catch(IOException e){
                    System.out.println("Exception in fi.close()");
                }
            }
        }

        System.out.println("copy done!!");
    }
}


上面例子没有使用缓冲流, 只有两个基本的字节输入输入流.

但是每一次读写都是利用到缓冲字节数组 bytArr.


实际上的效果也起到了缓冲作用, 缓冲的大小就是字节数组的大小(例子中是512k)

执行效果

ls: cannot access /home/gateman/tmp/Rockstar.flac: No such file or directory
gateman@TPEOS classes $ time java Enter_1 
copy done!!

real	0m0.246s
user	0m0.120s
sys	0m0.136s
gateman@TPEOS classes $ ls -lh /home/gateman/tmp/Rockstar.flac
-rw-rw-r-- 1 gateman gateman 32M Jul  1 23:48 /home/gateman/tmp/Rockstar.flac

实际效果更快了, 只需0.246s

貌似比使用缓冲流效果更好啊.


那么为何还需要缓冲流呢?

答案就是 缓冲流有预读机制,比起使用缓冲数组的缓冲效果更加明显, 如果处理一些大数据文件, 或者网络传输, 使用缓冲流的效果会更加好!


六, 总结

读到这里, 大家应该知道缓冲流的意义就是缓冲数据.


实际上很多常用的程序都有用到缓冲流的技术, 例如迅雷下载, 有缓冲区设置, 下载一定量的数据到内存, 然后一次过写入到硬盘, 就大大减少了写硬盘的次数.

还有在线视频的缓冲, 都是差不多的机制.




  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值