BufferedInputStream学习笔记
【本文转载于http://icanfly.iteye.com/blog/1207397】
BufferedInputStream是一个带有缓冲区域的InputStream,它的继承体系如下:
InputStream
|__FilterInputStream
|__BufferedInputStream
首先了解一下FilterInputStream:
FilterInputStream通过装饰器模式将InputStream封装至内部的一个成员变量:
- protected volatile InputStream in;
需要注意的是该成员变量使用了volatile关键字进行修饰,这意味着该成员变量的引用的内存可见性为多线程即时可见的。
其它地方FilterInputStream将所有的操作委托给了in这个成员进行操作。
了解了这些过后,来仔细看看BufferedInputStream的成员变量:
- private static int defaultBufferSize = 8192 //该变量定义了默认的缓冲大小
- protected volatile byte buf[]; //缓冲数组,注意该成员变量同样使用了volatile关键字进行修饰,作用为在多线程环境中,当对该变量引用进行修改时保证了内存的可见性。
- private static final AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater = AtomicReferenceFieldUpdater.newUpdater(BufferedInputStream.class, byte[].class, "buf")//缓存数组的原子更新器,该成员变量与buf数组的volatile关键字共同组成了buf数组的原子更新功能实现。
- protected int count;//该成员变量表示目前缓冲区域中有多少有效的字节。
- protected int pos;//该成员变量表示了当前缓冲区的读取位置。
- protected int markpos = -1;/*表示标记位置,该标记位置的作用为:实现流的标记特性,即流的某个位置可以被设置为标记,允许通过设置reset(),将流的读取位置进行重置到该标记位置,但是InputStream注释上明确表示,该流不会无限的保证标记长度可以无限延长,即markpos=15,pos=139734,该保留区间可能已经超过了保留的极限(如下)*/
- protected int marklimit;/*该成员变量表示了上面提到的标记最大保留区间大小,当pos-markpos> marklimit时,mark标记可能会被清除(根据实现确定)。*/
通过构造函数可以看到:初始化了一个byte数组作为缓冲区域
- public BufferedInputStream(InputStream in, int size) {
- super(in);
- if (size <= 0) {
- throw new IllegalArgumentException("Buffer size <= 0");
- }
- buf = new byte[size];
- }
这个类中最为重要的方法是fill()方法,它提供了缓冲区域的读取、写入、区域元素的移动更新等。下面着重分析一下该方法:
- private void fill() throws IOException {
- byte[] buffer = getBufIfOpen();
- if (markpos < 0) {
- /*如果不存在标记位置(即没有需要进行reset的位置需求)
- 则可以进行大胆地直接重置pos标识下一可读取位置,但是这样
- 不是会读取到以前的旧数据吗?不用担心,在后面的代码里会实现输入流的新
- 数据填充*/
- pos = 0;
- }else if (pos >= buffer.length){
- /* 位置大于缓冲区长度,这里表示已经没有可用空间了 */
- if (markpos > 0) {
- /* 表示存在mark位置,则要对mark位置到pos位置的数据予以保留,
- 以确保后面如果调用reset()重新从mark位置读取会取得成功*/
- int sz = pos - markpos;
- /*该实现是通过将缓冲区域中markpos至pos部分的移至缓冲区头部实现*/
- System.arraycopy(buffer, markpos, buffer, 0, sz);
- pos = sz;
- markpos = 0;
- } else if (buffer.length >= marklimit) {
- /* 如果缓冲区已经足够大,可以容纳marklimit,则直接重置*/
- markpos = -1;
- pos = 0;/* 丢弃所有的缓冲区内容 */
- } else {
- /* 如果缓冲区还能增长的空间,则进行缓冲区扩容*/
- int nsz = pos * 2;
- /*新的缓冲区大小设置成满足最大标记极限即可*/
- if (nsz > marklimit)
- nsz = marklimit;
- byte nbuf[] = new byte[nsz];
- //将原来的较小的缓冲内容COPY至增容的新缓冲区中
- System.arraycopy(buffer, 0, nbuf, 0, pos);
- //这里使用了原子变量引用更新,确保多线程环境下内存的可见性
- if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
- // Can't replace buf if there was an async close.
- // Note: This would need to be changed if fill()
- // is ever made accessible to multiple threads.
- // But for now, the only way CAS can fail is via close.
- // assert buf == null;
- throw new IOException("Stream closed");
- }
- buffer = nbuf;
- }
- count = pos;
- //从原始输入流中读取数据,填充缓冲区
- int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
- //根据实际读取的字节数更新缓冲区中可用字节数
- if (n > 0)
- count = n + pos;
- }
整个fill的过程,可以看作是BufferedInputStream对外提供滑动读取的功能实现,通过预先读入一整段原始输入流数据至缓冲区中,而外界对BufferedInputStream的读取操作实际上是在缓冲区上进行,如果读取的数据超过了缓冲区的范围,那么BufferedInputStream负责重新从原始输入流中载入下一截数据填充缓冲区,然后外界继续通过缓冲区进行数据读取。这样的设计的好处是:避免了大量的磁盘IO,因为原始的InputStream类实现的read是即时读取的,即每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),可想而知,如果数据量巨大,这样的磁盘消耗非常可怕。而通过缓冲区的实现,读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO,载入一段数据填充缓冲,那么下一次读取一般情况下就直接可以从缓冲区读取,减少了磁盘IO。减少的磁盘IO大致可以通过以下方式计算(限read()方式):
length 流的最终大小
bufSize 缓冲区大小
则通过缓冲区实现的输入流BufferedInputStream的磁盘IO数为原始InputStream磁盘IO的
1/(length/bufSize)
read方法解析:该方法返回当前位置的后一位置byte值(int表示).
- public synchronized int read() throws IOException {
- if (pos >= count) {
- /*表示读取位置已经超过了缓冲区可用范围,则对缓冲区进行重新填充*/
- fill();
- /*当填充后再次读取时发现没有数据可读,证明读到了流末尾*/
- if (pos >= count)
- return -1;
- }
- /*这里表示读取位置尚未超过缓冲区有效范围,直接返回缓冲区内容*/
- return getBufIfOpen()[pos++] & 0xff;
- }
一次读取多个字节(尽量读,非贪婪)
- private int read1(byte[] b, int off, int len) throws IOException {
- int avail = count - pos;
- if (avail <= 0) {
- /*这里使用了一个巧妙的机制,如果读取的长度大于缓冲区的长度
- 并且没有markpos,则直接从原始输入流中进行读取,从而避免无谓的
- COPY(从原始输入流至缓冲区,读取缓冲区全部数据,清空缓冲区,
- 重新填入原始输入流数据)*/
- if (len >= getBufIfOpen().length && markpos < 0) {
- return getInIfOpen().read(b, off, len);
- }
- /*当无数据可读时,从原始流中载入数据到缓冲区中*/
- fill();
- avail = count - pos;
- if (avail <= 0) return -1;
- }
- int cnt = (avail < len) ? avail : len;
- /*从缓冲区中读取数据,返回实际读取到的大小*/
- System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
- pos += cnt;
- return cnt;
- }
以下方法和上面的方法类似,唯一不同的是,上面的方法是尽量读,读到多少是多少,而下面的方法是贪婪的读,没有读到足够多的数据(len)就不会返回,除非读到了流的末尾。该方法通过不断循环地调用上面read1方法实现贪婪读取。
- public synchronized int read(byte b[], int off, int len)
- throws IOException
- {
- getBufIfOpen(); // Check for closed stream
- if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
- throw new IndexOutOfBoundsException();
- } else if (len == 0) {
- return 0;
- }
- int n = 0;
- for (;;) {
- int nread = read1(b, off + n, len - n);
- if (nread <= 0)
- return (n == 0) ? nread : n;
- n += nread;
- if (n >= len)
- return n;
- // if not closed but no bytes available, return
- InputStream input = in;
- if (input != null && input.available() <= 0)
- return n;
- }
- }
略过多少字节
- public synchronized long skip(long n) throws IOException {
- getBufIfOpen(); // Check for closed stream
- if (n <= 0) {
- return 0;
- }
- long avail = count - pos;
- if (avail <= 0) {
- // If no mark position set then don't keep in buffer
- //从上面的注释可以知道,这也是一个巧妙的方法,如果没有mark标记,
- // 则直接从原始输入流中skip
- if (markpos <0)
- return getInIfOpen().skip(n);
- // Fill in buffer to save bytes for reset
- fill();
- avail = count - pos;
- if (avail <= 0)
- return 0;
- }
- //该方法的实现为尽量原则,不保证一定略过规定的字节数。
- long skipped = (avail < n) ? avail : n;
- pos += skipped;
- return skipped;
- }
估计目前可用的字节数,原始流中可用的字节数+缓冲区中可用的字节数
- public synchronized int available() throws IOException {
- return getInIfOpen().available() + (count - pos);
- }
标记位置:
- public synchronized void mark(int readlimit) {
- marklimit = readlimit;
- markpos = pos;
- }
重置位置:该实现清晰的表明下一读取位置被推到了以前的标记位置,以实现重新读取区段的功能
- public synchronized void reset() throws IOException {
- getBufIfOpen(); // Cause exception if closed
- if (markpos < 0)
- throw new IOException("Resetting to invalid mark");
- pos = markpos;
- }
关闭流:首先通过线程安全的方式设置了内部的缓冲区引用为空,然后再对原始输入流进行关闭。
- 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()
- }
- }
BufferedInputStream学习笔记相关推荐
- java学习笔记16--I/O流和文件
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note16.html,转载请注明源地址. IO(Input Output)流 IO流用来处理 ...
- java学习笔记十三
11. 凡是继承了FilterOutputStream或FilterInputStream的类都是过滤流,也就是说他们不能直接跟目标(键盘,文件,网络等,节点流可以)数据打交道,只能包装 Intput ...
- 菜鸟学习笔记:Java提升篇6(IO流2——数据类型处理流、打印流、随机流)
菜鸟学习笔记:Java IO流2--其他流 字节数组输入输出流 数据类型处理流 基本数据类型 引用类型 打印流 System.in.System.out.System.err 随机流RandomAcc ...
- 菜鸟学习笔记:Java提升篇5(IO流1——IO流的概念、字节流、字符流、缓冲流、转换流)
菜鸟学习笔记:Java IO流1--IO流的概念.字节流.字符流.缓冲流.转换流 IO流的原理及概念 节点流 字节流 文件读取 文件写出 文件拷贝 文件夹拷贝 字符流 文件读取 文件写出 处理流 缓冲 ...
- Java 基础 第3阶段:高级应用——尚硅谷学习笔记(含面试题) 2023年
Java 基础 第 3 阶段:高级应用--尚硅谷学习笔记(含面试题) 2023 年 Java 基础 第 3 阶段:高级应用--尚硅谷学习笔记(含面试题) 2023 年 第 9 章 异常处理 9.1 异 ...
- 尚学堂JAVA基础学习笔记_2/2
尚学堂JAVA基础学习笔记_2/2 文章目录 尚学堂JAVA基础学习笔记_2/2 写在前面 第10章 IO技术 1. IO入门 2. IO的API 3. 装饰流 4. IO实战 5. CommonsI ...
- Java基础知识学习笔记总结
Java学习笔记总结 java基础复习 1. 抽象类可以有构造器,可以有一个非抽象的父类 2. 垃圾回收机制回收的是堆里面的内存,栈里面的数据自动入栈自动出栈 3. 引用类型的数据在堆当中,内存中操作 ...
- Java学习笔记整理-知识梳理+JDK1.8详细文档
链接:java开发手册 提取码:kes8 链接:JDK1.8详细文档 提取码:n9zo JavaSE 面向对象编程(Object-oriented programming) 封装 类(class) 类 ...
- 《Java学习笔记(第8版)》学习指导
<Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...
最新文章
- 2.7 usb摄像头之usb摄像头描述符打印
- vbs劫持快捷键并执行程序
- 日志库EasyLogging++学习系列(4)—— 格式说明符
- JavaScript学习笔记07【6个经典案例——电灯开关、轮播图、自动跳转首页、动态表格、表格全选、表单验证】
- OpenCV--cvScaler颜色赋值
- 201671010114 2016-2017-2 《Java程序设计》学习总结
- 1011 Sticks
- 【优化预测】基于matlab贝叶斯网络优化LSTM预测【含Matlab源码 1329期】
- ansys大变形开关要不要打开_ANSYS与ABAQUS比较之实例7橡胶垫圈的受压分析
- WhereHows 数据发现和管理工具
- 软考中级-软件设计师-查缺补漏
- W25Q64简介(译)
- java Virtual Machine Launcher
- uniapp中页面白屏问题
- 写给当初的你,现在的我
- 你好法语A1语法单元汇总(unité 7)
- dnf如何快速拾取物品_DNF宠物之最,快来Pick出你的挚爱
- 【思维进阶】就业市场调研-游戏行业
- 项目管理证书PMP的含金量高吗?
- Foxmail 登录 qq 账号时无法登录 提示我们设置了独立密码或使用授权码登录的解决方法...