在阅读BufferedInputStream的时候,有些地方有点疑惑,在研究好久之后,似乎清晰了一点,在此记录。
首先,我们来看下这里的主要的成员:
DEFAULT_BUFFER_SIZE = 8912 很简单,一个默认的buffer大小
MAX_BUFFER_SIZE = Integer.MAX_VALUE-8; 这个是buffer最大的申请空间,这里的-8大概是因为一些虚拟机会在数组头部添加一些内容吧。
buf[]; 真正的buffer,由关键字volatile修饰,表示线程可见。
bufUpdater;一个进行原子操作更新buffer的对象
count 这是buffer在流中当前已经读到的流大小
pos 位置,一个在buffer中的位置来标示当前所读到的位置。
markpos 为了进行重读流,进行的一个mark标记位置,在之后执行reset()的时候,pos会回到当前的markpos位置,以用来重复读取数据。
mark 的常规协定是:如果方法 markSupported 返回 true,则输入流总会在调用 mark 之后记住所有读取的字节,并且无论何时调用方法 reset ,都会准备再次提供那些相同的字节。但是,如果在调用 reset 之前可以从流中读取多于 readlimit 的字节,则根本不需要该流记住任何数据。
marklimit 标示在当前的markpos失效之前允许读到的最大位置。在这里碰到问题,为什么会有这个限制呢,通过查询相关资料我的理解就是当这个值太大的时候,或者pos过大的时候,buffer要保存被标记的字节的话就有些浪费资源,毕竟buffer是有限的。
接下来看看方法:
getBufIfOpen(): 这个方法用来获取当前的buffer,如果buffer关闭,则抛出异常。
构造器:用来创建一个制定大小的buffer字节数组。
在inputStream中主要的使用方法就是read(), 这里的这个方法
就是检查当前已读位置pos和已经读入的流的大小,来进行读取数据,或者是继续将流读入buffer来获取数据。因为如果当前的buffer不足达到要读取的地方,就会扩展buffer。
在这个方法中,fill()方法是最主要的一段逻辑。
下面解读一下fill()的目的。
1.当markpos < 0 ,即未被标记,则可以从buffer开头进行读入流,之后将流中的内容读进buffer,如果buffer空间不足则会进行扩展操作,稍后再提。将count赋值为读入的字节大小。
2.如果当前位置已经达到了buffer的大小(这里我感觉只有=是成立的吧,有更好的见解请分享给我😁),那么进行扩展或者清除动作。
2.1 markpos>0,标示存在mark,就将markpos之后的数据刷新到buffer的开始。确保mark之后的数据在以后执行reset()的时候可以重读到。注意,这里只是markpos>0,如果markpos=0,那么就不需要进行被mark的buffer的移位操作。
2.2 当markpos=0并且当buffer大小超过了marklimit,(这里可以看到marklimit指的是从开始到pos=marklimit这个位置之间的数据吧)那么就进行清除标记操作,以便腾出buffer空间。
2.3 buffer大小超过了最大限制,是不允许的操作。
2.4 fill()主要是用来填充buffer,这里就是填充的操作。首先计算一下当前pos有没有超过buffer最大大小MAX_BUFFER_SIZE的一半,没有超过就把新的大小nsz设置为pos的二倍;否则新的大小就是MAX_BUFFER_SIZE。然后比较nsz有没有超过marklimit,超过了就重新设置成marklimit的大小(在这里发现,这样做的目的是要控制buffer大小,不要超过marklimit,否则之后进行reset()的时候,会发现mark被清除导致异常发生。)然后进行一次buffer的扩展,将当前位置pos之前的数据拷贝到增大容量后的buffer。之后的updater.compareAndSet()是做什么用的呢?如果说是拷贝数据那么前一句话的拷贝是做什么用的?
通过在此分析发现,前面的拷贝操作只是将数据赋值给新的nbuf,并没有刷新对象本身的buffer,这个就是利用原子操作来进行这个动作确保原数组引用指向新数据。
看一下read1()和read()
从read方法的循环中,可以看到nread = read1(b, off + n, len - n),再去read1中观察,
计算还有多少可读avail,如果不够,那么先检查,如果markpos存在,同时要读的大小超过了buffer大小,那么就进行read()。这两个部分实现了缩小读取范围,直到len小于buffer的大小,那么就可以进行buffer的填充了,填充过后重新计算avail,重新计算读取的范围将buffer数据读入拷贝到接受流b中,如果inputStream中没有可用数据进行读取,那么就返回。
read()是贪婪读取,即从buffer中不断获取数据,buffer不断填充,直到inputStream没有可读数据。read1()是非贪婪的,只会读到buffer的结束,这两个方法组合使用来达到从inputStream中获取足够多的数据。
skip()
如果可读数据不足,没有mark的情况下直接从流inputStream中跳过;如果标记了mark,那么先填充,重新计算avail,进行pos的跳跃操作。
close()
利用原子操作把buffer进行指向null,inputStream指向null并且关闭对应的流来实现对整个资源的释放。
以上就是一些主要方法的理解,还有几个问题,希望大家帮忙提示一些东西。
注:这里的mark是为了实现对流的重读,假如没有标记,那么可以随意操作。反之,我们需要保证流的重读性,一些数据的重复获取还是很重要的,除非被重读标记的范围marklimit过大占用太多的资源,得不偿失,所以进行了标记复位操作来释放一些资源。
BufferedInputStream主要是用于进行针对磁盘io或是其他设备来优化的,用来提高效率,并且减少读取次数可以减少设备的损耗。