1. 首先观察BufferedInputStream 的继承体系,可以看出他是继承自一个FilterInputStream,而这个又是继承自InputStream

  2. 我们在之前的装饰器模式就讲过,这个BufferedInputStream 是很典型的装饰器模式的案例,那么在其内部应该有封装一个被包装对象

BufferedInputStream 成员变量

class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;

    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

    protected volatile byte buf[];

    private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf"); protected int count; protected int pos; protected int markpos = -1; protected int marklimit;
  1. 1.但是我们观察其成员变量,我们发现它的内部并没有维护一个被包装对象,这时候我们可以再其父类FilterInputStream 中找到如下代码,在父类中封装了待包装的对象
public class FilterInputStream extends InputStream {

    //此处封装了被包装对象
protected volatile InputStream in; protected FilterInputStream(InputStream in) {
this.in = in;
}
  1. 2.在BufferedInputStream 初始化的时候:可以看到调用super(in)父类的构造器,传入一个被包装对象,实现了装饰器模式
    public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}

  1. 接下来对所有的成员变量进行分析
    //根据名字我们可以得知,这个为默认_缓冲区_大小:为8kb
private static int DEFAULT_BUFFER_SIZE = 8192; //此处为最大的缓冲区数组的大小,也就是Integer的最大值,但是数组也要保存数组本身的信息,比如数组长度等信息,也是保存在数组内部,所以减去8个位置
private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; //这里就是我们最重要的缓冲数组,默认每次从文件中读取8kb,如果文件剩余不足8kb,则count等于读入的长度
protected volatile byte buf[]; //这里不知道,所以不讨论
private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf"); //这里是当前数组中可以读取的容量大小,如果此时buf数组中是满的,则count等于buf.length,如果此时不是满的,则count等于数组中总的可以读取的长度
protected int count; //这个是当前缓冲区中已经读到的位置,例:缓冲区有8192个字节,此时读取了8个字节,则pos等于9,也就是下一个要读取的字节
protected int pos; //标记位置,默认为-1
protected int markpos = -1; protected int marklimit;

  1. 我们接下来对构造函数进行分析:有两个,一个单参,一个双参,我们可以看到单参是会通过this去调用双参的构造器,并且第二个参数是默认的缓冲区大小

    在双参构造其中,super调用父类的构造器,传入被包装流,并且new了一个size长度的数组,如果没有传入缓冲区大小,则使用8192(aka 8kb)来创建缓冲区,否则按照传入的size来创建缓冲区
    public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
} public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
  1. 接下来就是BufferedInputStream流中最重要的read方法,首先是read()的无参方法,也就是默认先读取8192字节到缓存中,然后再一个一个从数组中取。

首先是基本使用read()

    public static void main(String[] args) throws Exception {
String path = "src/main/java/com/xj/dayio/demo.txt";
FileInputStream fis = new FileInputStream(path);
BufferedInputStream bis = new BufferedInputStream(fis); int len = -1;
while((len = bis.read()) != -1){
System.out.print((char)len);
}
}

然后是read()的源码:可以看出很简短,因为大部分代码都在fill()里面

    public synchronized int read() throws IOException {
//pos代表当前缓冲数组中已经读到的位置,count是缓冲数组所有可以读的字节数
//如果当前位置大于或者等于所有可以读的字节数,那么表示缓冲数组中已经没有可以读的字节了,所以需要再次从调用fill填充缓存数组
if (pos >= count) {
//填充满后
fill();
//如果此时pos位置还是大于或者等于count说明文件中已经没有字节可以读了,于是返回
if (pos >= count)
return -1;
}
//之后调用getBufIfOpen返回缓冲数组,并且返回当前pos位置的值
//getBufIfOpen我们看名字就可以得知,如果当前流没有关闭,就获取到缓冲数组
//此处与0xff具体原因未知,目前以我的理解是将两个字节的byte值提升为四个字节的int值
return getBufIfOpen()[pos++] & 0xff;
}

接下来我们具体看填充方法fill()的实现:我们可以看到这个是很长的,8着急,慢慢看

    private void fill() throws IOException {
//首先拿到缓冲数组
byte[] buffer = getBufIfOpen();
//判断标记位置,默认为-1(关于标记先不讲,一会再说)
if (markpos < 0) //默认为-1,所以将pos当前位置,置为数组的开头
pos = 0;
else if (pos >= buffer.length) //由于默认值为-1,所以下面这一大段!else if!都不用看啦!暂时。。。
if (markpos > 0) {
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1;
pos = 0;
} else if (buffer.length >= MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
} else {
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
throw new IOException("Stream closed");
}
buffer = nbuf;
}
//跳到这里啦!
//首先让count(也就是可以最多可读字节数量)等于我们的pos(aka 0)
count = pos;
//然后我们看这个方法的名字getInIfOpen,就是如果当前流没有被关闭,就获取到真正被包装的输入流对象
//然后调用真正的输入流对象的read方法(缓冲数组,当前位置(0),缓冲数组的长度(默认8192)-当前位置(0))
//默认来说的话,就是从文件中读取一个buffer数组长度的字节,并读取到buffer中,那么这里为什么会这么复杂呢,因为是为了标记用的(也就是我们上面跳过的那一大段else if)
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
//如果读取到返回的字节数量大于0的话,就说明读取到了字节,之后让count等于当前位置(pos)加上读取到的字节数量(n)
//否则n小于0或者等于0的话就说明文件中已经没有字节可读了
if (n > 0)
count = n + pos; //如果没有字节了的话此时pos和count都为0,然后我们回到我们上一个read的源码,就可以判断出pos >= count,就返回-1,读取结束
}
  1. 那接下来就是read(byte[] buf)的无参方法,也就是默认先读取8192字节到缓存中,但是与无参的方法区别是:

    • 我们使用无参方法的话是一个一个从缓冲数组中取;
    • 而使用传入缓冲数组的方法,我们每次可以每次从BufferedInputStream 的缓冲数组中每次取一个buf数组长度(4096)的字节;这样子效率更高,不需要一个一个访问bis的缓冲数组
    public static void main(String[] args) throws Exception {
String path = "src/main/java/com/xj/dayio/demo.txt";
FileInputStream fis = new FileInputStream(path);
BufferedInputStream bis = new BufferedInputStream(fis); //这里是自定义缓冲数组的大小,(注意:这个不是BufferedInputStream 内部的缓冲数组,是我们自己的)
//这里仿佛是使用4096时效率更高,并没有具体的考量,只是分别测试了一下
byte[] buf = new byte[4096];
int len = -1;
while((len = bis.read(buf)) != -1){
String str = new String(buf,0,len);
System.out.print(str);
}
}

啊,先更新到这里,好累呀

BufferedInputStream与BufferedOutputStream的缓存底层实现的更多相关文章

  1. Java IO流学习总结三:缓冲流-BufferedInputStream、BufferedOutputStream

    Java IO流学习总结三:缓冲流-BufferedInputStream.BufferedOutputStream 转载请标明出处:http://blog.csdn.net/zhaoyanjun6/ ...

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

    1. 概念简介 BufferedInputStream和BufferedOutputStream是带缓冲区的字节输入输出处理流.它们本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流 ...

  3. Java IO流 BufferedInputStream、BufferedOutputStream的基本使用

    BufferedInputStream.BufferedOutputStream的基本使用 BufferedInputStream是FilterInputStream流的子类,FilterInputS ...

  4. 节点流和处理流(BufferedReader和BufferedWriter,BufferedInputStream和BufferedOutputStream,ObjectlnputStream和objectOutputStream)

    一.基本介绍: 1.节点流可以从一个特定的数据源读写数据,如FileReader. FileWriter 如图:字节流是直接对数据源(文件,数组之类存放数据的地方)进行操作 2.处理流(也叫包装流)是 ...

  5. BufferedInputStream与BufferedOutputStream用法简介

    BufferedInputStream是带缓冲区的输入流,默认缓冲区大小是8M,能够减少访问磁盘的次数,提高文件读取性能:BufferedOutputStream是带缓冲区的输出流,能够提高文件的写入 ...

  6. BufferedInputStream与BufferedOutputStream

    BufferedInputStream是带缓冲区的输入流,默认缓冲区大小是8M,能够减少访问磁盘的次数,提高文件读取性能:BufferedOutputStream是带缓冲区的输出流,能够提高文件的写入 ...

  7. 关于BufferedInputStream和BufferedOutputStream的实现原理的理解

    在介绍FileInputStream和FileOutputStream的例子中,使用了一个byte数组来作为数据读入的缓冲区,以文件存取为例,硬盘存取的速度远低于内存中的数据存取速度.为了减少对硬盘的 ...

  8. Java IO(十) BufferedInputStream 和 BufferedOutputStream

    Java IO(十)BufferedInputStream 和 BufferedOutputStream 一.BufferedInputStream 和 BufferedOutputStream (一 ...

  9. java 输入输出IO流 字节流| 字符流 的缓冲流:BufferedInputStream;BufferedOutputStream;BufferedReader(Reader in);BufferedWriter(Writer out)

    什么是缓冲流: 缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率. 图解: 1.字节缓冲流BufferedInputStr ...

随机推荐

  1. 使用TypeConverter类

    3.2.2 使用TypeConverter类将XAML标签的Attribute与对象的Property进行映射注意本小节的例子对于初学者来说理解起来比较困难而且实用性不大,主要是为喜欢刨根问底的WPF ...

  2. HttpClient4.3教程 第三章 Http状态管理

    最初,Http被设计成一个无状态的,面向请求/响应的协议,所以它不能在逻辑相关的http请求/响应中保持状态会话.由于越来越多的系统使用http协议,其中包括http从来没有想支持的系统,比如电子商务 ...

  3. 2020年秋游戏开发-Gluttonous Snake

    此作业要求参考https://edu.cnblogs.com/campus/nenu/2020Fall/homework/11577 GitHub地址为https://github.com/15011 ...

  4. nginx《一安装》

    linux上nginx相关 wget https://nginx.org/download/nginx-1.14.1.tar.gz tar -zxvf nginx-1.14.1.tar.gz ./co ...

  5. roscore启动不完全问题

    运行roscore,得到如下日志,且一直卡着无法继续执行 ... logging to /home/xbit/.ros/log/79f2952c-589c-11ea-8213-d0abd5e7d222 ...

  6. Red Hat Enterprise Linux 7.2修改主机名(hostname)

    Red Hat Enterprise Linux 7.2在安装的时候,会默认生成主机名:localhost. 那么如何修改成自己想要的自己名? //格式为:用户名@主机名 比如: [root@loca ...

  7. 字符串截取子串(Java substring , indexOf)

    前言 因为之前java课设做的是股票分析系统,我找的接口返回的是一个.csv文件,因为这种文件里面的数据是以逗号分隔的,所以要对数据进行分析的时候需要截取子串,并且以逗号作为截取的标志.所以接下来就说 ...

  8. etcd学习(8)-etcd中Lease的续期

    etcd中的Lease 前言 Lease Lease 整体架构 key 如何关联 Lease Lease的续期 过期 Lease 的删除 checkpoint 机制 总结 参考 etcd中的Lease ...

  9. IOS 集成 Bilibili IJKPlayer播放器,播放rtmp视频流

    因为公司项目需要,我一个连iPhone都没用过的人竟然跑去开发iOS APP.近一段时间一直忙于赶项目,到今天差不多了,所以记录一下当时遇到的各种坑,先从ios 集成 ijkplayer播放器说起! ...

  10. SQL-DELETE触发器练习

    &练习一 如下所示三张表( student,grade,student_updata_before ): student表 grade表 Student_update_before表 # 触发 ...