【java】java.util.BufferedInputStream

本文深入探讨了缓冲流的高效利用方法,指出频繁调用flush会导致性能损失。通过分析缓冲流源码,揭示其自动刷新机制及合理刷新策略。实验证明,仅在关闭流前刷新一次即可达到最佳性能。代码示例直观展示了不同刷新策略对文件复制效率的影响。

一、补遗点

看很多人调用缓冲流的时候每write(无论哪一种write)一次就flush一次,这样是完全没有必要的,不仅浪费了缓冲流“缓冲”的特性,还每写一次就开闭IO一次,甚至做了一个中转才写IO,反而更加浪费性能。查看缓冲流(字节和字符)的源码,发现其实缓冲流是会自动把数据刷新到磁盘,完全没必要每写一次就flush一次,只需要在关闭缓冲流之前flush一次就够了。

缓冲流采用装饰器设计模式扩展了节点流,在缓冲流中定义了一个8192字节(字符)的buf缓冲数组和一个记录写入字节数量的游标count,当count>buf.length的时候,会自动把缓冲区的数据刷新到磁盘,然后将count归零,缓冲区重新开始放入数据。也就是缓冲流每8192个字节或字符会自动把数据刷入磁盘,只需要在关闭流之前,将文件最后小于8192的数据flush一次就可以了。

做一个测试,用以下三种方法拷贝一个963KB的mp3文件:

1. 关闭流前flush一次,关闭流;

结果:拷贝文件大小963KB。

2. 关闭流前flush一次,不关闭流;

结果:拷贝文件963大小KB。

3. 不flush,也不关闭流;

结果:拷贝文件大小960KB

结论:

1. 960KB=120*8192byte,如果没有flush,则最后3*1024byte因为小于8192byte,将驻留缓冲流缓冲区而不会被刷到磁盘;

2. 可以不调用flush,关闭流之前会默认flush一次,但是为了防止关闭流的时候出现异常,应该在关闭之前显式的调用flush方法将末尾数据刷新到磁盘;

详情请看源码的简单分析,你会感觉到设计的奇妙以及装饰器设计模式的精妙。

二、源码分析

字节缓冲流和字符缓冲流都会定义count和长度为8192的缓冲数组,本例为字节缓冲流源码。

/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.io;

/**
 * The class implements a buffered output stream. By setting up such 
 * an output stream, an application can write bytes to the underlying 
 * output stream without necessarily causing a call to the underlying 
 * system for each byte written.
 *
 * @author  Arthur van Hoff
 * @version %I%, %G%
 * @since   JDK1.0
 */
public 
class BufferedOutputStream extends FilterOutputStream {
    /**
     * The internal buffer where data is stored. 
     */
    protected byte buf[];<span style="background-color: rgb(255, 0, 0);">//定义缓冲数组</span>

    /**
     * The number of valid bytes in the buffer. This value is always 
     * in the range <tt>0</tt> through <tt>buf.length</tt>; elements 
     * <tt>buf[0]</tt> through <tt>buf[count-1]</tt> contain valid 
     * byte data.
     */
    protected int count;<span style="background-color: rgb(255, 0, 0);">//用于记录write的数据长度</span>
    
    /**
     * Creates a new buffered output stream to write data to the
     * specified underlying output stream.
     *
     * @param   out   the underlying output stream.
     */
    public BufferedOutputStream(OutputStream out) {
	this(out, 8192);<span style="background-color: rgb(255, 0, 0);">//buf缓冲区的默认长度是8192byte</span>
    }

    /**
     * Creates a new buffered output stream to write data to the 
     * specified underlying output stream with the specified buffer 
     * size. 
     *
     * @param   out    the underlying output stream.
     * @param   size   the buffer size.
     * @exception IllegalArgumentException if size <= 0.
     */
    public BufferedOutputStream(OutputStream out, int size) {
	super(out);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
	buf = new byte[size];<span style="background-color: rgb(255, 0, 0);">//自定义缓冲区大小</span>
    }

    /** Flush the internal buffer */
    private void flushBuffer() throws IOException {<span style="background-color: rgb(255, 0, 0);">//私有的flush方法,调用节点流的write方法,将数据写到磁盘</span>
        if (count > 0) {
	    out.write(buf, 0, count);
	    count = 0;
        }
    }

    /**
     * Writes the specified byte to this buffered output stream. 
     *
     * @param      b   the byte to be written.
     * @exception  IOException  if an I/O error occurs.
     */
    public synchronized void write(int b) throws IOException {<span style="background-color: rgb(255, 0, 0);">//缓冲流自己的write方法,不再是写磁盘,而是写到缓冲区buf</span>
	if (count >= buf.length) {<span style="background-color: rgb(255, 0, 0);">//如果缓冲流自己的write方法已经写入的数据长度小于buf.length,则表示缓冲区没有写满,继续写缓冲</span>
	    flushBuffer();<span style="background-color: rgb(255, 0, 0);">//如果缓冲流自己的write方法已经写入的数据长度大于等于buf.length则表示缓冲区写满,调用节点流的write方法将数据写入磁盘</span>
	}
	buf[count++] = (byte)b;<span style="background-color: rgb(255, 0, 0);">//缓冲区没有写满,将数据写入缓冲区,记录缓冲流write方法写入的数据长度count也相应的增长</span>
    }

    /**
     * Writes <code>len</code> bytes from the specified byte array 
     * starting at offset <code>off</code> to this buffered output stream.
     *
     * <p> Ordinarily this method stores bytes from the given array into this
     * stream's buffer, flushing the buffer to the underlying output stream as
     * needed.  If the requested length is at least as large as this stream's
     * buffer, however, then this method will flush the buffer and write the
     * bytes directly to the underlying output stream.  Thus redundant
     * <code>BufferedOutputStream</code>s will not copy data unnecessarily.
     *
     * @param      b     the data.
     * @param      off   the start offset in the data.
     * @param      len   the number of bytes to write.
     * @exception  IOException  if an I/O error occurs.
     */
    public synchronized void write(byte b[], int off, int len) throws IOException {
	if (len >= buf.length) {<span style="background-color: rgb(255, 0, 0);">//判断传入的数据数组是否会将缓冲区写满,写满则自动flush,否则继续写</span>
	    /* If the request length exceeds the size of the output buffer,
    	       flush the output buffer and then write the data directly.
    	       In this way buffered streams will cascade harmlessly. */
	    flushBuffer();
	    out.write(b, off, len);
	    return;
	}
	if (len > buf.length - count) {<span style="background-color: rgb(255, 0, 0);">//如果传入的数据数组的长度大于缓冲区剩余空间,先将缓冲区的数据刷入磁盘,在写入数据</span>
	    flushBuffer();
	}
	System.arraycopy(b, off, buf, count, len);<span style="background-color: rgb(255, 0, 0);">//将数据拷贝到缓冲区</span>
	count += len;<span style="background-color: rgb(255, 0, 0);">//记录缓冲区已经写入数据长度的游标增长</span>
    }

    /**
     * Flushes this buffered output stream. This forces any buffered 
     * output bytes to be written out to the underlying output stream. 
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FilterOutputStream#out
     */
    public synchronized void flush() throws IOException {<span style="background-color: rgb(255, 0, 0);">//用于显式调用的flush方法,用于将数据刷新到磁盘,无论缓冲区是否被写满,<strong>在缓冲流的生命周期中,只需要调用该方法一次就行了</strong></span>
        flushBuffer();
	out.flush();
    }
}

附注:

        如有错漏,烦请不吝指正!

com.alibaba.excel.exception.ExcelAnalysisException: Can not create temporary file! at com.alibaba.excel.util.FileUtils.writeToFile(FileUtils.java:138) at com.alibaba.excel.analysis.v07.XlsxSaxAnalyser.readOpcPackage(XlsxSaxAnalyser.java:206) at com.alibaba.excel.analysis.v07.XlsxSaxAnalyser.<init>(XlsxSaxAnalyser.java:89) at com.alibaba.excel.analysis.ExcelAnalyserImpl.choiceExcelExecutor(ExcelAnalyserImpl.java:103) at com.alibaba.excel.analysis.ExcelAnalyserImpl.<init>(ExcelAnalyserImpl.java:55) at com.alibaba.excel.ExcelReader.<init>(ExcelReader.java:30) at com.alibaba.excel.read.builder.ExcelReaderBuilder.build(ExcelReaderBuilder.java:214) at com.alibaba.excel.read.builder.ExcelReaderBuilder.sheet(ExcelReaderBuilder.java:251) at com.alibaba.excel.read.builder.ExcelReaderBuilder.sheet(ExcelReaderBuilder.java:243) at com.hikvision.ms.mall.recon.service.channel.shouyinbao.excelread.ShouYinBaoBillExcelReadServiceImpl.readExcel(ShouYinBaoBillExcelReadServiceImpl.java:23) at com.hikvision.ms.mall.recon.service.channel.shouyinbao.AbstractShouYinBaoReconLoadService.getBusinessChannelFundDetailList(AbstractShouYinBaoReconLoadService.java:194) at com.hikvision.ms.mall.recon.service.channel.shouyinbao.AbstractShouYinBaoReconLoadService.shouYinBaoReconLoad(AbstractShouYinBaoReconLoadService.java:95) at com.hikvision.ms.mall.recon.service.channel.shouyinbao.impl.ShouYinBaoReconLoadServiceImpl.lambda$fundUnitLoad$0(ShouYinBaoReconLoadServiceImpl.java:80) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: java.util.zip.ZipException: invalid entry size (expected 18730692 but got 77207 bytes) at java.util.zip.ZipInputStream.readEnd(ZipInputStream.java:384) at java.util.zip.ZipInputStream.read(ZipInputStream.java:196) at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) at java.io.BufferedInputStream.read1(BufferedInputStream.java:286) at java.io.BufferedInputStream.read(BufferedInputStream.java:345) at com.alibaba.excel.util.FileUtils.writeToFile(FileUtils.java:134) ... 15 common frames omitted 什么原因报错
08-26
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package cn.hutool.extra.servlet; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.collection.ArrayIter; import cn.hutool.core.collection.IterUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.NetUtil; import cn.hutool.core.net.multipart.MultipartFormData; import cn.hutool.core.net.multipart.UploadSetting; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.Writer; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 依赖了这个怎么办
07-09
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值