JAVA I/O 缓冲装饰器 BufferedInputStream和BufferedOutputStream

本文主要是介绍JAVA I/O 缓冲装饰器 BufferedInputStream和BufferedOutputStream,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

BufferedInputStream和BufferedOutputStream是一种缓冲装饰器,它能让我们将输入流中的数据暂时写入缓存中,再将缓存中的数据再写入输出流中。从而避免了多次真实的I/O操作,节省CPU,网络开销。

1.BufferedInputStream

API:

方法名注释
public BufferedInputStream(InputStream in)创建一个BufferedInputStream,并设置可缓冲的字节量为8192,同时创建一个缓冲字节数组。
public BufferedInputStream(InputStream in, int size) 创建一个BufferedInputStream,并设置可缓冲的字节量,同时创建一个缓冲字节数组。
public synchronized int read()读取缓冲区中下一个字节
public synchronized int read(byte b[], int off, int len)读取缓冲区中的字节到b中,并返回读取的字节数,如果没有可读取的字节则返回-1
public synchronized long skip(long n)跳跃n个字节读取,返回实际跳跃的字节数。注意这里的实际跳跃的字节数并不一定时持有的输入流的实际跳跃的字节数。
public synchronized int available()获取实际可读取的字节数,最大为Integer.MAX_VALUE。
public synchronized void mark(int readlimit) 标记当前位置,并设置标记失效限制的可读取到缓冲中的字节数。
public synchronized void reset() 重置标记,将当前读取的pos重置到标记位置,再次读取的字节为标记的下一个字节。
public boolean markSupported()测试该装饰类是否支持标记,BufferedInputStream支持标记,总是返回true。
public void close()  将当前缓冲区源自更新为null,并关闭持有的输入流。

      源码解读:

public
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[];/*** 使用AtomicReferenceFieldUpdater来为buf[]提供CAS原子操作,避免被装饰的字节流异步关闭造                        * 成所带来的损失。*/private static finalAtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =AtomicReferenceFieldUpdater.newUpdater(BufferedInputStream.class,  byte[].class, "buf");/*** 当前缓冲区中记录有效的字节数,注意这里并不是输入流中已读取得字节数。*/protected int count;/*** 缓冲流当前的读取位置,指向下一个将要读取的字节。* * 该值总处于0-count之间。如果该值比count小,那么buf[pos]指向下一个产出的字节;如果该值* 恰好等于count,那么下一个读取或者跳跃的操作将需要更多的字节来读取。**/protected int pos;/*** 标记位置,mark方法标记时pos的值。* * 该值总处于-1到pos之间。如果输入流中没有标记,那么这个值为-1;如果输入流中有标记,那么 * buf[markpos]将会是reset方法被调用后第一个读取到的字节。如果该值不是-1,那么* buf[markpos]到buf[pos-1]的所有字节都必须保存在缓存数组中(即使count,pos,markpos* 等值进行调整后,他们可能被移动到了另一个缓存数组中);除非pos和markpos之间的差值超过* 了marklimit,否则这些缓存字节将不会被丢弃。*/protected int markpos = -1;/*** 保证标记有效的最大pos和markpos的差值,一旦当前读取的pos到标记markpos之间的字节数超过* 了该值,那么标记将被废弃,markpos将被重置为-1.*/protected int marklimit;/*** 检查并确认当前的输入流并没有被关闭,如果没有被关闭,返回该输入流 */private InputStream getInIfOpen() throws IOException {InputStream input = in;if (input == null)throw new IOException("Stream closed");return input;}/*** 检查并确认当前的传冲字节数组并没有被关闭,如果没有被关闭,返回该字节数组*/private byte[] getBufIfOpen() throws IOException {byte[] buffer = buf;if (buffer == null)throw new IOException("Stream closed");return buffer;}/*** 创建一个BufferedInputStream,并设置可缓冲的字节量为8192,同时创建一个缓冲字节数组。*/public BufferedInputStream(InputStream in) {this(in, DEFAULT_BUFFER_SIZE);}/*** 创建一个BufferedInputStream,并设置可缓冲的字节量,同时创建一个缓冲字节数组。*/public BufferedInputStream(InputStream in, int size) {super(in);if (size <= 0) {throw new IllegalArgumentException("Buffer size <= 0");}buf = new byte[size];}/*** 填充字节数组.* 1.如果当前没有标记,丢弃当前缓冲区的数据,将输入流剩余的数据填充至当前缓冲区。* 2.如果当前标记>0,当前缓冲区buffer已满, 那么将丢弃标记位置之前的数据,然后将标记位置以后的数据存入当*   前的缓冲区,即丢弃标记之前的数据,将标记之后的数据和输入流剩余的数据填充至重新填充缓冲区。  * 3.如果当前标记=0,当前缓冲区buffer已满,且缓冲区大小超过标记限制值,那么废弃标记,丢弃当前缓冲区的数据,*   将输入流剩余的数据填充至当前缓冲区。* 4.如果当前标记=0,当前缓冲区buffer已满,且缓冲区大小超过最大限制,抛出异常。* 5.如果当前标记=0,当前缓冲区buffer已满,且缓冲区大小未超过标记限制和最大限制,那么尝试扩增缓冲区大小为*   原来的一倍或者到标记限制值或最大限制值,继续读取输入流数据到传冲区中。*/private void fill() throws IOException {//获取未关闭的字节数组byte[] buffer = getBufIfOpen();if (markpos < 0)pos = 0;            /* 如果未被标记,那么将当前位置重置为起始位置。 */else if (pos >= buffer.length)  /* 如果当前位置超过或恰好处于缓冲区边界 */if (markpos > 0) {  /* 如果有标记 */int sz = pos - markpos;//将buffer中markpos开始的sz个字节copy到buffer中0到sz-1的位置。System.arraycopy(buffer, markpos, buffer, 0, sz); //pos设置为szpos = sz;//标记到0markpos = 0;} else if (buffer.length >= marklimit) { //如果缓冲数组长度大于等于marklimitmarkpos = -1;   /* 那么标记失效*/pos = 0;        /* 丢弃buffer内容 */} else if (buffer.length >= MAX_BUFFER_SIZE) { //如果缓冲数组长度大于等于最大传冲限制,抛出异常。throw new OutOfMemoryError("Required array size too large");} else {            /* 扩增buff *///当前传冲区扩增一倍,如果超过了最大缓冲量,则扩增到最大缓冲量。int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?pos * 2 : MAX_BUFFER_SIZE;//扩增以后如果缓冲量大于最大标记,那么缓冲量改为最大标记量。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指向新的缓冲区buffer = nbuf;}//统计已缓存的字节数count = pos;//将当前输入流的剩余数据读入buffer中pos到结束的位置。int n = getInIfOpen().read(buffer, pos, buffer.length - pos);//再次统计已缓存的字节数if (n > 0)count = n + pos;}/*** 返回缓冲中pos的下一个字节。*/public synchronized int read() throws IOException {if (pos >= count) {//如果当前位置大于等于count,那么说明没有可以读取的字节,填充缓冲区。fill();//如果当前位置仍然大于等于count,那么说明流中没有更多的数据,返回-1if (pos >= count)return -1;}//如果当前位置小于当前统计的字节数,返回下一个缓冲区中下一个字节。return getBufIfOpen()[pos++] & 0xff;}/*** 将缓冲区中的数据读入到b中*/private int read1(byte[] b, int off, int len) throws IOException {//获取可读取的字节数int avail = count - pos;if (avail <= 0) {//如果没有可读取的字节,且要读取的长度大于等于缓冲区大小同时没有标记值,//那么将缓冲区中的数据读入b[]中if (len >= getBufIfOpen().length && markpos < 0) {return getInIfOpen().read(b, off, len);}//填充当前缓冲区fill();//再次获取可读取的字节数avail = count - pos;//如果没有可读取的字节,返回-1if (avail <= 0) return -1;}//将可读取的字节从缓冲区copy到b中int cnt = (avail < len) ? avail : len;System.arraycopy(getBufIfOpen(), pos, b, off, cnt);//统计pospos += cnt;//返回copy的字节数return cnt;}/*** 读取缓冲区中的字节到b中,并返回读取的字节数,如果没有可读取的字节则返回-1*/public synchronized int read(byte b[], int off, int len)throws IOException{//检查并获取当前缓冲区getBufIfOpen(); //判断b[]中是否由足够的空间,如果len为0则直接返回0if ((off | len | (off + len) | (b.length - (off + len))) < 0) {throw new IndexOutOfBoundsException();} else if (len == 0) {return 0;}int n = 0;for (;;) {//将缓冲区中的数据读入b中,并返回读取的字节数int nread = read1(b, off + n, len - n);//如果没有字节则返回-1.if (nread <= 0)return (n == 0) ? nread : n;//如果有字节,则用n来统计读取的字节数n += nread;//如果读取的字节数大于等于len,那么返回读取的字节数if (n >= len)return n;//如果流中没有可读取的字节,返回nInputStream input = in;if (input != null && input.available() <= 0)return n;}}/*** 跳跃n个字节读取,返回实际跳跃的字节数。*/public synchronized long skip(long n) throws IOException {//检查并获取当前的缓冲区getBufIfOpen(); //如果跳跃值小于0,则返回0.if (n <= 0) {return 0;}//获取可跳跃的字节数long avail = count - pos;if (avail <= 0) {//如果没有可跳跃的空间,且没有标记,那么当前流的跳跃n并返回跳跃值。if (markpos <0)return getInIfOpen().skip(n);//如果没有可跳跃的空间,但有标记值,那么填充当前缓冲区fill();//再次获取可跳跃的字节数avail = count - pos;//如果没有可跳跃的空间,返回0.if (avail <= 0)return 0;}//如果有可跳跃的空间,那么调整pos,返回实际跳跃的值。long skipped = (avail < n) ? avail : n;pos += skipped;return skipped;}/*** 获取实际可读取的字节数,最大为Integer.MAX_VALUE。*/public synchronized int available() throws IOException {int n = count - pos;int avail = getInIfOpen().available();return n > (Integer.MAX_VALUE - avail)? Integer.MAX_VALUE: n + avail;}/*** 标记当前位置,并设置标记失效限制的可读取到缓冲中的字节数。*/public synchronized void mark(int readlimit) {marklimit = readlimit;markpos = pos;}/*** 重置标记,将当前读取的pos重置到标记位置,再次读取的字节为标记的下一个字节。*/public synchronized void reset() throws IOException {getBufIfOpen(); // Cause exception if closedif (markpos < 0)throw new IOException("Resetting to invalid mark");pos = markpos;}/*** 测试该装饰类是否支持标记,BufferedInputStream支持标记,总是返回true。*/public boolean markSupported() {return true;}/*** 将当前缓冲区源自更新为null,并关闭持有的输入流。*/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()}}
}

2.BufferedOutputStream

API:

方法名注释
public BufferedOutputStream(OutputStream out)创建一个OutputStream的装饰类BufferedOutputStream,缓冲区大小为8192
public BufferedOutputStream(OutputStream out, int size)创建一个OutputStream的装饰类BufferedOutputStream,缓冲区大小为size。
private void flushBuffer()刷新缓冲区,将缓冲区的数据写入流中
public synchronized void write(int b)将b转为字节写入缓冲区
public synchronized void write(byte b[], int off, int len)从字节数组b[]中的off位置开始,将len个字节写入缓冲区中。
1.如果要写的字节超过缓冲区的大小,那么将缓冲区的数据刷入输出流,并将要写的数据直接写入输出流中。
2.如果要写的字节没有超过缓冲区的量,但缓冲区没有足够的位置存储这些字节,那么先将缓冲区的数据刷入输出流  再将要写的数据写入缓冲区
public synchronized void flush()将缓冲区的数据刷入输出流中,然后将输出流中的数据刷出到指定位置。

源码解读:

package java.io;public
class BufferedOutputStream extends FilterOutputStream {/*** 字节缓冲区*/protected byte buf[];/*** 当前缓冲区可用的字节数*/protected int count;/*** 创建一个OutputStream的装饰类BufferedOutputStream,缓冲区大小为8192*/public BufferedOutputStream(OutputStream out) {this(out, 8192);}/*** 创建一个OutputStream的装饰类BufferedOutputStream,缓冲区大小为size。*/public BufferedOutputStream(OutputStream out, int size) {super(out);if (size <= 0) {throw new IllegalArgumentException("Buffer size <= 0");}buf = new byte[size];}/** 刷新缓冲区,将缓冲区的数据写入流中,并重置count为0 */private void flushBuffer() throws IOException {if (count > 0) {out.write(buf, 0, count);count = 0;}}/***将b转为字节写入缓冲区*/public synchronized void write(int b) throws IOException {if (count >= buf.length) {//如果缓冲区已满,先将缓冲区的数据刷入输出流中flushBuffer();}//再将字节写入缓冲区的count++buf[count++] = (byte)b;}/*** 从字节数组b[]中的off位置开始,将len个字节写入缓冲区中。* 1.如果要写的字节超过缓冲区的大小,那么将缓冲区的数据刷入输出流,并将要写的数据直接写入输出流中。* 2.如果要写的字节没有超过缓冲区的量,但缓冲区没有足够的位置存储这些字节,那么先将缓冲区的数据刷入输出流*   再将要写的数据写入缓冲区*/public synchronized void write(byte b[], int off, int len) throws IOException {//如果要写的字节超过缓冲区的量if (len >= buf.length) {//那么先将缓冲区的数据刷入输出流,然后将b中的数据直接写入输出流中flushBuffer();out.write(b, off, len);return;}//如果要写的字节没有超过缓冲区的量但超过了缓冲区的空闲量if (len > buf.length - count) {//那么先将缓冲区的数据刷入输出流flushBuffer();}//然后将b中的数据写入缓冲区System.arraycopy(b, off, buf, count, len);//统计已缓存的字节数count += len;}/*** 将缓冲区的数据刷入输出流中,然后将输出流中的数据刷出到指定位置。*/public synchronized void flush() throws IOException {flushBuffer();out.flush();}
}

 

这篇关于JAVA I/O 缓冲装饰器 BufferedInputStream和BufferedOutputStream的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/917201

相关文章

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug

Java中Object类的常用方法小结

《Java中Object类的常用方法小结》JavaObject类是所有类的父类,位于java.lang包中,本文为大家整理了一些Object类的常用方法,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. public boolean equals(Object obj)2. public int ha

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

SpringBoot实现动态插拔的AOP的完整案例

《SpringBoot实现动态插拔的AOP的完整案例》在现代软件开发中,面向切面编程(AOP)是一种非常重要的技术,能够有效实现日志记录、安全控制、性能监控等横切关注点的分离,在传统的AOP实现中,切... 目录引言一、AOP 概述1.1 什么是 AOP1.2 AOP 的典型应用场景1.3 为什么需要动态插