BufferedInputStream的read方法原理

记一次InputStream流读取不完整留下的惨痛教训 首先,问问大家下面这段流读取的代码是否存在问题呢?try {// 根据inputStream的长度创建字节数组// 调用read 读取字节数组}复制代码实际上的确是有问题的,而且在线上环境结结实实的坑了我们一把。在代码编写过程中,available()方法仅用于估算接收数据的总长度或数据块的长度,不要用于任何需要准确计算的场合,更不要用于开辟一个可以刚好容纳所有数据的缓冲区。 阅读详情
BufferedInputStream是一个带有缓冲区的输入流,通常使用它可以提高我们的读取效率,现在我们看下BufferedInputStream的实现原理: 
BufferedInputStream内部有一个缓冲区,默认大小为8M,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给用户.由于从缓冲区里读取数据远比直接从物理数据源(譬如文件)读取速度快,所以BufferedInputStream的效率很高! 

在具体看源码之前,我们还需要了解BufferedInputStream的mark操作:voidmark(int markLimit) 
当你调用mark方法时,内部会保存一个markPos标志,它的值为目前读取字节流的pos位置,倘若你调用reset方法,这时候会把pos重置为markPos的值,这样你就可以重读已经读过的字节.好像说的不是很清楚,那我们打个比方:有一段字节流是abcdefg,当你读取完字母a调用mark方法(此时markPos指向字母b),接着你继续读取字母b,字母c,字母d,然后此时你调用reset方法(内部把pos重置为markPos),当你再读取下一个字节的时候,你会发现你读取到的是b而不是字母e,这样通过mark方法我们就是实现了重复读(re-readthe same bytes) 

mark方法中还有个参数markLimit,它的值表示在调用mark方法后reset方法前最多允许读取的字节数(根据我的测试,以及查看源代码发现,这个最大字节数,其实是由markLimit和buffer.size中较大的那个决定的),如果超过这个限制,则在调用reset方法时会报:Resetingto invalid mark 异常 
说完了这么多,让我们来赶紧看看源码吧: 
  1. // BufferedInputStream主要有这两个构造方法  
  2.   
  3. public BufferedInputStream(InputStream in) {  
  4.      this(in, defaultBufferSize);   // 默认缓冲区为8M  
  5.     }  
  6.   
  7. public BufferedInputStream(InputStream in, int size) {  
  8.     super(in);  
  9.     if (size <= 0) {  
  10.         throw new IllegalArgumentException("Buffer size <= 0");  
  11.     }  
  12.     buf = new byte[size];  
  13. }  
你需要指定InputStream(装饰模式的体现)以及bufferSize(可选) 
当我们调用read()方法时,它在内部做了一下事情: 
  1. public synchronized int read() throws IOException {  
  2.     if (pos >= count) {         // 检查是否有可读缓冲数据  
  3.         fill();                 // 没有缓冲数据可读,则从物理数据源读取数据并填充缓冲区  
  4.         if (pos >= count)       // 若物理数据源也没有多于可读数据,则返回-1,标示EOF  
  5.         return -1;  
  6.     }  
  7.   
  8.  // 从缓冲区读取buffer[pos]并返回(由于这里读取的是一个字节,而返回的是整型,所以需要把高位置0)  
  9.     return getBufIfOpen()[pos++] & 0xff;     
  10. }  
  11.   
  12. private byte[] getBufIfOpen() throws IOException {  
  13.     byte[] buffer = buf;      // buf为内部缓冲区  
  14.     if (buffer == null)  
  15.         throw new IOException("Stream closed");  
  16.         return buffer;  
  17. }  

其中pos为缓冲区buffer下一个可读的数组下标,我们可以一直从缓冲区里读取数据,直到pos变为count(此时只能从物理数据源读取数据),下面我们就分析下,当缓冲区里没有数据可读时,BufferInputStream是如何处理的: 

1.若用户没有开启re-read功能(即未调用mark方法),当pos ==count时,我们只需要将pos重新置为0,然后从物理源读取数据(假设读到了n个字节),最后把count设置成 n + pos 即可(其实就是n,因为pos之前被设置成了0),当下次你在调用read方法时,就直接从缓冲读取,非常快速(如下图); 
BufferedInputStream实现原理分析

2.若用户调用了mark方法,情况就变得很复杂了,为什么呢?这意味着我们需要保存从markPos到pos这段数据(以供用户re-read),现在我们分情况讨论: 
a.此时pos <buffer.length,这意味着缓冲区还有多余空间,所以我们可以继续从物理数据源读取数据放入到缓冲区中(如下图); 
BufferedInputStream实现原理分析

b.pos ==buffer.length,这意味着缓冲区已经没有多余空间,所以只能清空缓冲区内容,但是不要忘了,我们还必须保留原来 
markPos到pos那段数据,以供用户re-read,所以我需要这样做: 
  1. // 计算需要保留多少字节的数据  
  2. int sz = pos - markPos;     
  3. // 然后拷贝到缓冲头部                                
  4. System.arraycopy(buffer, markpos, buffer, 0, sz);   
  5. // 重置pos以及markPos        
  6. pos=sz;                                                                                  
  7. markPos=0;                     
接着从缓冲区的sz 到buffer.length又变成可用区间,用来存放从物理数据源读到的数据(如下图) 
BufferedInputStream实现原理分析

到这里似乎问题完美的解决了,其实不然,我们忘记考虑markPos失效问题,以及若pos - markPos ==buffer.length,那么移了等于白移动,还是没有挪出多余空间,所以实我们应该这样做(后面讨论都是建立在pos ==buffer.length的基础上): 


2.1 若markPos > 0, 那么 pos -makrPos一定小于缓冲区大小,这样意味着我们按照刚才的算法挪动后,缓冲区就有了空余空间 
BufferedInputStream实现原理分析
2.2 若makrPos == 0, 这意味着需要保存的数据满满的充斥着缓冲区,所以这时候我们是无法通过挪动位置来使缓冲区有多余空间的,所以我们只可以清空或扩展缓冲区 
2.2.1 当buffer.length >= marklimit时,此时意味着markPos已经失效,用户不可以在进行re-read,所以此时我们就可以简单释放整个缓冲区了:pos=0,markPos=-1; 
2.2.2其余情况,意味着markPos还有效,所以我们只能通过扩展缓冲区的方式来使缓冲区有多余空间 
BufferedInputStream实现原理分析
说了这么多,我们还是看下相关代码吧: 
  1. private void fill() throws IOException {  
  2.         byte[] buffer = getBufIfOpen();        // 得到当前缓冲区  
  3.      if (markpos < 0)                          // 对应情况1  
  4.          pos = 0;           
  5.      else if (pos >= buffer.length)            // 对应情况2  
  6.          if (markpos > 0) {                    // 对应情况2.1  
  7.                int sz = pos - markpos;  
  8.                System.arraycopy(buffer, markpos, buffer, 0, sz);  
  9.                pos = sz;  
  10.                markpos = 0;  
  11.          } else if (buffer.length >= marklimit) {  // 对应情况2.2.1  
  12.                markpos = -1;      
  13.                pos = 0;      
  14.          } else {                                  // 对应情况2.2.2  
  15.                int nsz = pos * 2;  
  16.                if (nsz > marklimit)  
  17.                    nsz = marklimit;  
  18.                byte nbuf[] = new byte[nsz];  
  19.                System.arraycopy(buffer, 0, nbuf, 0, pos);  
  20.             ...  
  21.          }  
  22.     count = pos;  
  23.      int n = getInIfOpen().read(buffer, pos, buffer.length - pos);  
  24.     if (n > 0)  
  25.         count = n + pos;  
  26. }  

好了关于BufferedInputStream就说道这里,它的 read(byte b[], intoff, intlen)其实内部实现也大概如此:先从缓冲区读,如果读不到则从物理数据源读取并刷新到缓冲区(可能需要对缓冲区原来内容作必要的挪动或者对缓冲区大小进行扩展)
深入理解Java I/O流之BufferedInputStream类详解 在 Java 开发中,IO 操作是十分常见且重要的一个内容。在 IO 操作中,类是一个十分重要的类,它可以提供缓冲和流的级联两个功能,使得读取操作变得更加高效,提供了一定的性能优化。本文将对类进行详细介绍。本文将从类的定义入手,介绍其主要的功能、使用场景和优缺点,然后从源代码解析、应用场景案例和类代码方法介绍等方面深入探讨类的内部机制和使用方法,并提供测试用例对类进行实际测试。最后对全文内容进行总结,帮助读者更好地理解类。类继承自。 阅读详情

相关推荐

STM32F103C8T6心电采集实战:从PCB设计到上位机开发的完整指南(附TI杯开源资料)

本文详细介绍了基于STM32F103C8T6的心电采集系统开发全流程,从PCB设计到上位机开发,特别适合大学生电子设计大赛参赛者参考。内容涵盖硬件设计精要、软件算法优化及上位机开发技巧,并附TI杯开源资料,帮助开发者低成本实现医疗级心电信号采集。

kafka6courier的博客 194

Java BufferedInputStream read()方法与示例

BufferedInputStream类read()方法 (BufferedInputStream Class read() method) Syntax: 句法: public int read(); public int read(byte[] b_arr, int offset, int length); read() method is available in...

cumtb2009的博客 3522

BitLocker加密盘解密实战:从隐藏分区到密钥恢复的全过程

本文深入探讨了BitLocker加密盘在密钥丢失或遗忘情况下的解密实战方法。文章系统性地介绍了从理解加密机制、制作取证镜像,到深度扫描隐藏分区、分析内存转储寻找密钥残留,最终利用恢复密钥解锁加密卷的全过程。重点剖析了非标准恢复路径,为数据恢复和数字取证工作提供了专业的技术框架和实战思路。

weixin_29268385的博客 338

Java中BufferedInputStream的read​()方法和read​(byte[] b)方法读取读取文本文件

BufferedInputStream流的内部缓冲区是字节数组 java.io.BufferedInputStream extends InputStream BufferedInputStream:字节缓冲输入流 继承自父类的成员方法: abstract int read​() 从输入流读取数据的下一个字节。 int read​(byte[]b) 从输入流中读取一些字节数,并将它们存储到缓冲器阵列 b 。 void close.

崔凯洋的博客 7214

5分钟搞定!用FunASR在本地快速部署达摩院Paraformer语音识别模型(附常见问题解决)

本文详细介绍了如何在本地快速部署阿里达摩院开源的Paraformer语音识别模型。通过FunASR工具包,用户可在5分钟内完成环境配置、模型下载与基础识别,并掌握批量处理、性能调优及构建API服务等进阶技巧。文章重点解析了Paraformer的非自回归架构带来的速度优势,并提供了音频格式转换、依赖冲突、显存不足等常见问题的实战解决方案,助力开发者实现高效、离线的语音转写应用。

p1q2r的博客 708

Flink CDC 1.18.1 Oracle 数据同步到postgresql

8、修改Flink的配置文件,主要是各种服务的绑定地址,默认为localhost,统统改为0.0.0.0,如:rest.address: 0.0.0.0 #localhost。将flink-sql-connector-oracle-cdc-3.0.1.jar复制到flink-1.18.1/lib目录中。6、下载 flink-connector-jdbc-3.1.1-1.17.jar,postgresql-42.7.3.jar。7、安装postgresql就不说了,相信你已经有了数据库了。

ynzhangyao的专栏 1800

BufferedInputStream实现原理

BufferedInputStream实现原理: 使用BufferedInputStream的read()方法读取硬盘上的字节数据时,实际上调用的是FileInputStream的read(byte[] b)方法,把读取的数据先存放到一个字节数组中(byte[]),也就是字节流缓冲区中.再从字节数组(字节流缓冲区)取出字节数据,BufferedInputStream的read()方法读取字节

tuanjie108的专栏 1362

linux驱动由浅入深系列:块设备驱动之一(高通eMMC分区实例)

linux驱动由浅入深系列:块设备驱动之一(高通eMMC分区实例)linux驱动由浅入深系列:块设备驱动之二(块设备驱动结构分析)块设备驱动的模型还是基本基于字符设备驱动的,可以简单理解为块设备仅仅增加了操作缓冲区,对用户请求顺序进行队列重拍等等。字符设备驱动的相关分析可以查看本博客相关的博文。 按照本博客的行文习惯,在具体分析块设备驱动代码之前,我们会从整体上了解一下研究对象的特征,以及用户空间...

Radia的专栏 1万+

BufferedInputStream源码分析

BufferedInputStream是一个带有缓冲区域的InputStream,它的继承体系如下: InputStream |__FilterInputStream         |__BufferedInputStream 首先了解一下FilterInputStream: FilterInputStream通过装饰器模式将InputStream封装至内部的一个成员变量:

地平线的专栏 2214

【Python_Zebra斑马打印机编程学习笔记(二)】基于BarTender将btw文件转换为zpl文件

本文介绍如何基于 BarTender 软件将 btw 格式的文件转换为 zpl 格式的文件,有需要的读者可以点赞收藏,也欢迎在评论区进行讨论。

禾戊之昂的博客 3378

点云语义分割:RandLANet模型推理C++部署(2)

一、数据读取及预处理 二、模型推理 三、结果保存

zhaoguanhua的博客 3801

BufferedInputStream

  BufferedInputStream:缓冲字节输入流,是一个高级流(处理流),与其他低级流配合使用。   查看源码可见BufferedInputStream 没有无参构造方法,它必须传入一个IputStream 一般是FileInputStream。   /**   * 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。创建一个内部缓冲区数组...

weixin_30512089的博客 124

Unreal5从入门到精通之一文搞懂Unreal的C++代码编译之谜

我们在开发Unreal项目时,如果时蓝图模板的项目,一般问题不大,因为当你修改完蓝图,点击编译Compile+保存Save,就可以继续开发了。这对于开发者来说也非常友好。但是,如果你使用的C++模板开发项目,代码编译一切都变得魔幻起来,IDE(VS/Rider)里面有编译,编辑器里面也有编译,有什么区别呢?我们来列举几个新手常碰到的问题。1.代码我明明新建了,编辑器里怎么看不到?2.昨天我新家的功能运行的好好的,今天怎么感觉还是老逻辑。或者昨天加的新代码,新蓝图不见了。

TxNet 1607

关于BufferedInputStream和FileInputStream中Read方法的问题

BufferedInputStream的实现中有一个用于存储数据的内部缓冲区数组:protected volatile byte[] buf。这个缓冲区数组的作用在于对源进行数据块访问,而不是一字节一字节的访问,也就是进行一次I/O将一块数据存到缓冲区中,再从缓冲区中read,当缓冲区为空时再重新读新的数据块。这个数组是jdk里的BufferedInputStream类的实现里的,是作为Buffe

qfikh的博客 2481

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

BufferedInputStream源码学习笔记

BufferedInputStream 带有缓冲功能,支持标记(mark),最重要的函数是fill(),为类私有,可实现将部分数据从输入流填充到缓冲区(一个byte[]保存),它也能将缓冲区扩大。它是一个装饰类(装饰者模式在这里不做介绍)。 BufferedInputStream定义: public class BufferedInputStream extends FilterInputStre...

weixin_42273608的博客 310

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

BufferedInputStream 用法

1.首先来看类结构 : BufferedInputStream是继承自FilterInputStream。 FilterInputStream继承自InputStream属于输入流中的链接流,同时引用了InputStream,将InputStream封装成一个内部变量,同时构造方法上需要传入一个InputStream。这是一个典型的装饰器模式,他的任何子类都可以对一个继承自Input

mujianwei的专栏 1万+

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

回顾BufferedInputStream-int read(byte[] buffer)BufferedOutputStream-void write(byte[] buff ,int offset

学习java基础,是一个反复学习的过程。 今天回顾BufferedInputStream的 int read(byte[] buffer) 和BufferedOutputStream的void write(byte[] buff ,int offset ,int len) int read(byte[] buffer) 参数: buff,字符流,将字符传入到字符数组当中。一般大

liuxiaohui_dh的专栏 1559

【转】BufferedInputStream学习笔记

原文地址:http://icanfly.iteye.com/blog/1207397 BufferedInputStream是一个带有缓冲区域的InputStream,它的继承体系如下:  InputStream  |__FilterInputStream          |__BufferedInputStream  首先了解一下FilterInputStream: 

saviola1999的专栏 783

BufferedInputStream学习笔记

【本文转载于http://icanfly.iteye.com/blog/1207397】 BufferedInputStream是一个带有缓冲区域的InputStream,它的继承体系如下:  InputStream  |__FilterInputStream          |__BufferedInputStream  首先了解一下FilterInputStream: 

xorxos的专栏 507

字节输入流 BufferedInputStream

字节输入流 BufferedInputStream

LL__Sunny的博客 411

BufferedInputStream详解

BufferedInputStream是一个带有缓冲区域的InputStream,它的继承体系如下: InputStream |__FilterInputStream |__BufferedInputStream 首先了解一下FilterInputStream: FilterInputStream通过装饰器模式将InputStream封装至内部的...

weixin_30273931的博客 119

Java日看一类(2)之IO包中的BufferedInputStream类

类的继承和包的导入BufferedInputStream类引用了一个其他类import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;同时BufferedInputStream类本身继承FilterInputStream(以上类在后续文章中讲到后会贴链接,本篇文章不做过多解释)理解一个类的作用最方便快捷的的方式就是查看其类开头的注释...

github_37666068的博客 498

Java字节流:BufferedInputStream BufferedOutputStream

-----------------------------------------------------------------------------------BufferedInputStream 类声明:public class BufferedInputStream extends FilterInputStream位于java.io包下官方对其说明:  A Buffered...

dianzhenluo3164的博客 231

Java中IO框架——BufferedInputStream源码解析

BufferedInputStream 继承自 FilterInputStream 。 InputSteam 的源码分析见博客:http://blog.youkuaiyun.com/yx0628/article/details/79343633 FilterInputStream 的源码分析见博客:http://blog.youkuaiyun.com/yx0628/article/details/79366049 ...

在梅边的专栏 603
上一篇: BufferedInputStream与BufferedOutputStream用法简介
下一篇: 我从阿里面试回来,想和Java程序猿谈一谈
ole_triangle_java
博客等级 码龄10年 82粉丝 83原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值