接着上篇文章,我们继续来学习Java中的字节流操作。
装饰者流其实是基于一种设计模式”装饰者模式”而实现的一种文件IO流,而我们的缓冲流只是其中的一种,我们一起来看看。
在这之前,我们使用的文件读写流FileInputStream和FileOutputStream都是一个字节一个字节的从磁盘读取或写入,非常耗时。
而我们的缓冲流可以预先从磁盘一次性读出指定容量的字节数到内存中,之后的读取操作将直接从内存中读取,提高效率。下面我们一起看看缓冲流的具体实现情况:
依然先以BufferedInputStream为例,我们简单提一下它的几个核心属性:
-
<李>私有静态int DEFAULT_BUFFER_SIZE=8192;李>
<李>保护不稳定的字节缓冲区[],李>
<李>私有静态int MAX_BUFFER_SIZE=整数。李MAX_VALUE - 8; >
<李>保护int数;李>
<李>保护int pos;李>
<李>保护int markpos=1;李>
<李>保护int marklimit;李>
buf就是用于缓冲读的字节数组,它的值将随着流的读取而不停的被填充,继而后续的读操作可以直接基于这个缓冲数组。
DEFAULT_BUFFER_SIZE规定了默认缓冲区的大小,即缓冲区的数组长度.MAX_BUFFER_SIZE指明了缓冲区的上限。
数指向缓冲数组中最后一个有效字节索引后一位.pos指向下一个待读取的字节索引位置。
markpos和marklimit用于重复读操作。
接着我们看看BufferedInputStream的几个示例构造器:
公共BufferedInputStream (InputStream) { 这(DEFAULT_BUFFER_SIZE); }
公共BufferedInputStream (InputStream, int大小){ 超级(的); 如果(大小& lt;=0) { 把新的IllegalArgumentException(“缓冲区大小& lt;=0”); } buf=新字节(大小); }
整体上来说,前者只需要传入一个“被装饰”的InputStream实例,并使用默认大小的缓冲区。后者则可以显式指明缓冲区的大小。
除此之外,超级()会将这个InputStream实例保存进父类FilterInputStream在属的性字段中,并且所有实际的磁盘读操作都由这个InputStream实例发出。
下面我们来看最重要的读操作以及缓冲区是如何被填充的。
公共同步int read()抛出IOException { 如果(pos祝辞=count) { 填满(); 如果(pos祝辞=数) 返回1; } 返回getBufIfOpen () (pos + +),0 xff; }
这个方法想必大家已经很熟悉了,从流中读取下一个字节并返回,但细节上的实现还是稍稍有些不同。
数指向了缓冲数组中有效字节索引后一位置处,pos指向下一个待读取的字节索引位置。理论上pos是不可能大于计数的,最多等于。
如果pos等于数,那说明缓冲数组中所有有效字节都已经被读取过了,此时即需要丢弃缓冲区中那些“无”用的数据,从磁盘重新加载一批新数据填充缓冲区。
而事实上,填充方法就是做的这个事情,它的代码比较多,就不带大家去解析了,你理解了它的作用,想必分析它的实现也是容易的。
如果填补方法调用之后,pos依然等于数,那么说明InputStream实例并没有从流中读取出任何数据,也即文件流中无数据可读。关于这一点,参见填补方法246行。
总的来说,如果成功填充了缓冲区,那么我们的阅读方法将直接从缓冲区取出一个字节返回给调用者。
公共同步int读(字节b [], int, int len) {//..... }
这个方法也是“熟”人了,不再多余的解释了,实现是类似的。
跳过方法用于跳过指定长度的字节数进行文件流的继续读取:
公共同步长跳过(n) {//..... }
注意一点的是,跳过方法尽量去跳过n个字节,但不保证一定跳过n个字节,方法返回的是实际跳过的字节数。如果缓冲数组中剩余可用字节数小于n,那么最终将跳过缓冲数组中实际可跳过的字节数。
最后要说一说这个亲密的方法:
公共空间close()抛出IOException { byte[]缓冲区; 在((缓冲=buf) !=null) { 如果(bufUpdater.compareAndSet(缓冲,null)) { InputStream输入=; 在=零; 如果(输入!=null) input.close (); 返回; }//其他重试,以防新缓冲区是下套管填充() } }Java中的字节流文件读取教程(二)