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

相关文章

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Mac系统下卸载JAVA和JDK的步骤

《Mac系统下卸载JAVA和JDK的步骤》JDK是Java语言的软件开发工具包,它提供了开发和运行Java应用程序所需的工具、库和资源,:本文主要介绍Mac系统下卸载JAVA和JDK的相关资料,需... 目录1. 卸载系统自带的 Java 版本检查当前 Java 版本通过命令卸载系统 Java2. 卸载自定

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Java Spring ApplicationEvent 代码示例解析

《JavaSpringApplicationEvent代码示例解析》本文解析了Spring事件机制,涵盖核心概念(发布-订阅/观察者模式)、代码实现(事件定义、发布、监听)及高级应用(异步处理、... 目录一、Spring 事件机制核心概念1. 事件驱动架构模型2. 核心组件二、代码示例解析1. 事件定义

SpringMVC高效获取JavaBean对象指南

《SpringMVC高效获取JavaBean对象指南》SpringMVC通过数据绑定自动将请求参数映射到JavaBean,支持表单、URL及JSON数据,需用@ModelAttribute、@Requ... 目录Spring MVC 获取 JavaBean 对象指南核心机制:数据绑定实现步骤1. 定义 Ja

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

springboot项目中整合高德地图的实践

《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一:高德开放平台的使用二:创建数据库(我是用的是mysql)三:Springboot所需的依赖(根据你的需求再

spring中的ImportSelector接口示例详解

《spring中的ImportSelector接口示例详解》Spring的ImportSelector接口用于动态选择配置类,实现条件化和模块化配置,关键方法selectImports根据注解信息返回... 目录一、核心作用二、关键方法三、扩展功能四、使用示例五、工作原理六、应用场景七、自定义实现Impor

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项