J05-Java IO流总结五 《 BufferedInputStream和BufferedOutputStream 》

1. 概念简介

  BufferedInputStream和BufferedOutputStream是带缓冲区的字节输入输出处理流。它们本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流。事实上,这两个处理流(BufferedInputStream和 BufferedOutputStream),加上BufferedReader和BufferedWriter,这四个流在设计时使用到的正是装饰设计模式通过装饰设计模式,得以在其他的流的基础上增加缓冲的功能

  当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。

2. 使用FileInputStream和FileOutputStream复制文件的原理图

3. 使用BufferedInputStream和BufferedOutputStream赋值文件的原理图

3.1 MyBos

  由源码可知,BufferedOutputStream自身并没有实现将数据写入目的地的功能,真正的写入功能其实还是由它所装饰的字节输出流os来实现的。通过缓冲区的缓冲作用,增加了内存与内存之间的交互,减少了内存与磁盘的直接交互,以此提升I/O的效率。

  下面是在参考了BufferedOutputStream的源码之后,自己实现的缓冲字节输出流,其原理非常简单,即:首先在MyBos内部包装了一个真正具有将数据输出到目的地功能的字节输出流os;在MyBos中还定义了一个字节缓冲数组buf,默认大小为8K,当调用write(byte b)或write(byte[] buf, int off, int len)方法将数据写出时,并不是直接将数据写出到目的地,首选是先将数据写到MyBos中自定义的缓冲区buf中,只有当缓冲区满了或者手动刷新的时候,才会将缓冲区的数据一次性写入到目的地。

自己实现的缓冲字节输出流的代码如下所示:

import java.io.IOException;
import java.io.OutputStream;

public class MyBos {

    private byte[] buf; //缓冲字节数组
    private int count;  //记录目前缓冲数组中的有效字节数
    private OutputStream os;//被装饰的底层输出字节流

    //构造方法
    public MyBos(OutputStream os) throws Exception {
        this(os, 8192);     //若不显式指定缓冲数组的大小,则默认分配8k空间
    }

    public MyBos(OutputStream os, int size) throws Exception {
        if(size < 0) {
            throw new Exception("缓冲区空间大小分配错误:" + size);
        }
        this.os = os;
        buf = new byte[size];
    }

    /**
     * 写出单个字节数据
     * @param b 写出的字节数据
     * @throws IOException
     */
    public void write(int b) throws IOException {
        if(count == buf.length) {//若count == buf.length,说明缓冲区buf已满,则先将缓冲区数据写出
            os.write(buf);      //调用os的wirte(byte[] buf)方法讲数据写出!
            count = 0;          //重置count
        }

        buf[count++] = (byte)b;     //将字节数据写入缓冲区,写入到count所在索引位置上
    }

    /**
     * 将字节数组b[offset, offset+len)部分写出
     * @param b     字节数组
     * @param offset    开始位置
     * @param len       长度
     * @throws Exception
     */
    public void write(byte[] b, int offset, int len) throws Exception {

        if(offset < 0 || len < 0 || b.length < (offset + len)) {
            throw new Exception("数组索引越界异常!");
        }

        if(len > buf.length) {  //若写出的字节数组b数据大于缓冲数组
            flush();            //则先调用flush()方法将缓冲数组的数据写出
            os.write(b, offset, len);   //直接将数组b的数据通过os的wirte方法写出,不写入缓冲数组了
            return;
        }

        if(len > buf.length - count) {  //若缓冲数组剩余空间不足len长度
            flush();                    //则先将缓冲数组的数据写出
        }

        System.arraycopy(b, offset, buf, count, len);
        count += len;

    }

    /**
     * 刷新缓冲区
     * @throws IOException
     */
    public void flush() throws IOException {
        if(count > 0) { //若缓冲区中有数据,则调用底层输出流os将数据写出
            os.write(buf, 0, count);
            os.flush();
            count = 0;  //重置count
        }
    }

    /**
     * 关闭流
     * @throws IOException
     */
    public void close() throws IOException {
        if(null != os) {
            flush();    //先将缓冲数组的数据写出
            os.close(); //接着关闭底层数据流os
        }
    }
}

3.2  MyBis

  由BufferedInputStream的源码可知,该类的内部同样有一个字节缓冲区buf,每次读取数据时,它会先检查缓冲区中是否有数据,若有则直接从缓冲区中取数,若是没有,则先通过底层输入流将指定大小(默认是8192个字节)的数据从底层读取到缓冲区中,再从缓冲区拿数。

  跟BufferedOutputStream一样,BufferedInputStream自身也没有实现将数据从源文件读入内存的功能,真正的读取功能其实还是由它所装饰的底层字节输入流is来实现的。通过缓冲区的缓冲作用,增加了内存与内存之间的交互,减少了内存与磁盘的直接交互,以此提升I/O的效率。

自己实现的BufferedInputStream示例代码:

import java.io.IOException;
import java.io.InputStream;

public class MyBis {
    //用于访问底层数据的底层流
    private InputStream is;
    //字节数组缓冲区
    byte[] buf;
    //默认缓存大小8k
    public static final int DEFAULT_BUF_SIZE = 8192;
    //记录缓冲区中 待读取的字节数
    private int count;
    //用于记录读取到当前 buf 中字节数据的位置
    private int pos;

    //构造方法
    public MyBis(InputStream is) throws Exception {
        this(is, DEFAULT_BUF_SIZE);
    }

    public MyBis(InputStream is, int size) throws Exception {
        if(size < 0) {
            throw new Exception("\"缓冲区空间大小分配错误:\" + size");
        }
        this.is = is;
        buf = new byte[DEFAULT_BUF_SIZE];
    }

    /**
     * 从缓冲区中读取下一个字节的数据,如果数据到到了末尾,返回 -1
     * @return 读取到的字节数据的 int 形式,如果读取到了流的末尾,返回 -1
     * @throws IOException
     */
    public int read() throws IOException {
        if(pos == count) {//若读取的下一个字节数已经到达有效数据的末尾
            count = is.read(buf);//则通过底层的输入流,一次性读取 8192 个字节数据到缓冲区中来。
            if(-1 == count) {//若is 没有读取到数据,直接返回-1
                return -1;
            }
            //若is读取到数据了,将pos设置为第一个字节
            pos = 0;
        } 

        //返回读取到的字节数据的 int 形式
        return buf[pos++] & 0xff;
    }

    /**
     * 处理流的关闭问题:只关闭处理流即可,处理流的关闭会将底层的流关闭掉。
     * @throws IOException
     */
    public void close() throws IOException {
        if(null != is) {
            is.close();
        }
    }
}

测试自己写的字节缓冲输入流:

import java.io.FileInputStream;
import java.io.IOException;

public class TestMyBis {
    public static void main(String[] args) {
        MyBis mb = null;

        try {
            mb = new MyBis(new FileInputStream("./src/res/1.txt"));

            int value = 0;

            while(-1 != (value = mb.read())) {
                System.out.print((char)value);
            }

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

代码运行效果:

4. 字节缓冲流应用示例

示例代码:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class BufferedStreamTest {
    public static void main(String[] args) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //使用缓冲流
            bis = new BufferedInputStream(new FileInputStream("e:/test_file/data_structure.avi"));
            bos = new BufferedOutputStream(new FileOutputStream("e:/test_file/data_structure_copy.avi"));
            int len = 0;
            byte[] buf = new byte[1024*1024];//因为读取的源文件较大,所以这里分配的空间大一点

            while(-1 != (len = bis.read(buf))) {
                bos.write(buf, 0, len);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(null != bos) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != bis) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

最后,由BufferedInputStream和BufferedOutputStream这两个类的源码或API可以看出,它们没有新增任何新的方法,使用的都是常见的read()、read(byte[] b)、write(int b)、write(byte[] buf, int off, int len)等方法。

原文地址:https://www.cnblogs.com/suhaha/p/9651568.html

时间: 2024-08-21 22:30:30

J05-Java IO流总结五 《 BufferedInputStream和BufferedOutputStream 》的相关文章

Java IO流详解(五)——缓冲流

缓冲流也叫高效流,是处理流的一种,即是作用在流上的流.其目的就是加快读取和写入数据的速度. 缓冲流本身并没有IO功能,只是在别的流上加上缓冲效果从而提高了效率.当对文件或其他目标频繁读写或操作效率低,效能差时.这时使用缓冲流能够更高效的读写信息.因为缓冲流先将数据缓存起来,然后一起写入或读取出来.所以说,缓冲流还是很重要的,在IO操作时加上缓冲流提升性能. Java IO流中对应的缓冲流有以下四个: 字节缓冲流:BufferedInputStream.BufferedOutputStream 字

黑马程序员-Java IO流

--Java培训.Android培训.iOS培训..Net培训.期待与您交流!-- 一.概述 Java中与IO相关的类有很多,都集中在java.io中,都是以流的形式操作的,流是有一定的顺序,像一个管道一样,它的本质是传输数据.根据数据类型的不同可以分为字节流和字符流,根据流向的不同可以分为输入流和输出流. 字符流:因为数据有不同的编码,可以对字符进行不同的操作,其本质还是基于字节流,然后再查询相应的码表.一般用于处理纯文本数据. 字节流:可以处理所有类型数据,二进制文件(图片,音频等). 输入

Java:IO流与文件基础

Java:IO流与文件基础 说明: 本文所有内容包含图片均为MrSaber自己编写,转载请练习我哦. 本章内容将会持续更新,大家可以关注一下并给我提供建议,谢谢啦. 走进流 什么是流 流:从源到目的地的字节的有序序列. 在Java中,可以从其中读取一个字节序列的对象称作 输入流,可以向其中写入一个字节序列的对象称作 输出流. ? 这些字节序列的来源可以是:文件.网络连接.内存块等. ? 抽象类InputStream和OutputStream是构成输入/输出(I/O)的基础. ? 因为面向字节的流

装饰器模式及JAVA IO流例子★★★☆☆

一.什么是装饰模式 通过关联机制给类增加行为,其行为的扩展由修饰对象来决定: 二.补充说明 与继承相似,不同点在于继承是在编译期间扩展父类,而装饰器模式在运行期间动态扩展原有对象: 或者说,继承是对类进行扩展,装饰模式是对对象进行扩展: 三.角色 抽象构件 具体构件 抽象装饰类 具体装饰类 说明:具体构件.抽象装饰类.具体装饰类的共同父类是抽象构件,具体装饰类继承抽象装饰类并在运行期间装饰具体构件: 四.例子 例子说明: 画家接口Painter,为抽象构件,有两个方法,获取画家描述信息及绘画:

java IO流学习笔记

import java.io.*;class hello{ public static void main(String[] args) { System.out.println(File.separator); System.out.println(File.pathSeparator); }}[运行结果]: \ ; String fileName="D:"+File.separator+"hello.txt"; File f=new File(fileName)

学习笔记-java IO流总结 转载

1.什么是IO Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列.Java的I/O流提供了读写数据的标准方法.任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法. Java.io是大多数面向数据流的输入/输出类的主要软件包.此外,Java也对块传输提供支持,在核心库 java.nio中采用的便是块IO. 流IO的好处是简单易用,缺点是效率较低.块IO效率很高,但编程比较

java IO流详解(一)

从本篇博文开始,详细讲解JAVA IO流的基本操作,力求每一个例子都给大家一个DEMO,在最终完成的时候,我会贴出最终的源码,方便大家交流学习. 上一篇博文中转载了大神的博文和图片,非常好! 文章链接如下:Java IO流 下面一个个的用实例进行讲解每个IO流的基本用法. 1 File文件 public static void main(String[] args) throws IOException { File file = new File("."); myPrint(file

Java IO流分析整理

Java IO流的分类 Java中的流,可以从不同的角度进行分类. 按流向分类: 输入流: 程序可以从中读取数据的流.输出流: 程序能向其中写入数据的流. 按数据传输单位分类: 字节流:以字节(8位二进制)为单位进行处理.主要用于读写诸如图像或声音的二进制数据. 字符流:以字符(16位二进制)为单位进行处理. 都是通过字节流的方式实现的.字符流是对字节流进行了封装,方便操作.在最底层,所有的输入输出都是字节形式的. 后缀是Stream是字节流,而后缀是Reader,Writer是字符流. 按功能

Java基础17:Java IO流总结

Java基础17:Java IO流总结 IO流概述 在这一小节,我会试着给出Java IO(java.io)包下所有类的概述.更具体地说,我会根据类的用途对类进行分组.这个分组将会使你在未来的工作中,进行类的用途判定时,或者是为某个特定用途选择类时变得更加容易. 输入和输出 术语"输入"和"输出"有时候会有一点让人疑惑.一个应用程序的输入往往是另外一个应用程序的输出?那么OutputStream流到底是一个输出到目的地的流呢,还是一个产生输出的流?InputStre

Java IO流(二)

目录 Java IO流(二) 7. 字节缓冲流 7.1 概述 7.2 BufferedOutputStream类 7.3 BufferedInputStream类 8. 文件复制练习(增强版 使用缓冲流) 9. 字符缓冲流 9.1 BufferedWriter类 9.2 BufferedReader类 10. 练习:文本排序 11. 转换流 11.1 字符编码和字符集 11.2 编码引出的问题 11.3 转换流的原理 11.4 OutputStreamWriter类 11.5 InputStre