本文主要是介绍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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!