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

相关文章

Java线程池核心参数原理及使用指南

《Java线程池核心参数原理及使用指南》本文详细介绍了Java线程池的基本概念、核心类、核心参数、工作原理、常见类型以及最佳实践,通过理解每个参数的含义和工作原理,可以更好地配置线程池,提高系统性能,... 目录一、线程池概述1.1 什么是线程池1.2 线程池的优势二、线程池核心类三、ThreadPoolE

Springboot请求和响应相关注解及使用场景分析

《Springboot请求和响应相关注解及使用场景分析》本文介绍了SpringBoot中用于处理HTTP请求和构建HTTP响应的常用注解,包括@RequestMapping、@RequestParam... 目录1. 请求处理注解@RequestMapping@GetMapping, @PostMappin

Java调用DeepSeek API的8个高频坑与解决方法

《Java调用DeepSeekAPI的8个高频坑与解决方法》现在大模型开发特别火,DeepSeek因为中文理解好、反应快、还便宜,不少Java开发者都用它,本文整理了最常踩的8个坑,希望对... 目录引言一、坑 1:Token 过期未处理,鉴权异常引发服务中断问题本质典型错误代码解决方案:实现 Token

SpringBoot整合AOP及使用案例实战

《SpringBoot整合AOP及使用案例实战》本文详细介绍了SpringAOP中的切入点表达式,重点讲解了execution表达式的语法和用法,通过案例实战,展示了AOP的基本使用、结合自定义注解以... 目录一、 引入依赖二、切入点表达式详解三、案例实战1. AOP基本使用2. AOP结合自定义注解3.

Java实现字符串大小写转换的常用方法

《Java实现字符串大小写转换的常用方法》在Java中,字符串大小写转换是文本处理的核心操作之一,Java提供了多种灵活的方式来实现大小写转换,适用于不同场景和需求,本文将全面解析大小写转换的各种方法... 目录前言核心转换方法1.String类的基础方法2. 考虑区域设置的转换3. 字符级别的转换高级转换

SpringBoot简单整合ElasticSearch实践

《SpringBoot简单整合ElasticSearch实践》Elasticsearch支持结构化和非结构化数据检索,通过索引创建和倒排索引文档,提高搜索效率,它基于Lucene封装,分为索引库、类型... 目录一:ElasticSearch支持对结构化和非结构化的数据进行检索二:ES的核心概念Index:

Java方法重载与重写之同名方法的双面魔法(最新整理)

《Java方法重载与重写之同名方法的双面魔法(最新整理)》文章介绍了Java中的方法重载Overloading和方法重写Overriding的区别联系,方法重载是指在同一个类中,允许存在多个方法名相同... 目录Java方法重载与重写:同名方法的双面魔法方法重载(Overloading):同门师兄弟的不同绝

Spring配置扩展之JavaConfig的使用小结

《Spring配置扩展之JavaConfig的使用小结》JavaConfig是Spring框架中基于纯Java代码的配置方式,用于替代传统的XML配置,通过注解(如@Bean)定义Spring容器的组... 目录JavaConfig 的概念什么是JavaConfig?为什么使用 JavaConfig?Jav

Java数组动态扩容的实现示例

《Java数组动态扩容的实现示例》本文主要介绍了Java数组动态扩容的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1 问题2 方法3 结语1 问题实现动态的给数组添加元素效果,实现对数组扩容,原始数组使用静态分配

Java中ArrayList与顺序表示例详解

《Java中ArrayList与顺序表示例详解》顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,:本文主要介绍Java中ArrayList与... 目录前言一、Java集合框架核心接口与分类ArrayList二、顺序表数据结构中的顺序表三、常用代码手动