个人ImageIO.write ByteArrayOutputStream为何这么慢?

File.createTempFile(prefix, suffix),建立一个临时文件,再使用完以后清理便可。
可是遇到以下两个坑:centos

String prefix = "temp"; String suffix = ".txt"; File tempFile = File.createTempFile(prefix, suffix);

以上代码中,须要注意的两个地方:
一、prefix必须大于3个字符
二、suffix须要带上 . , 好比:.png、.zip缓存



问题来源:

      1.系统生成二维码,须要不一样的图片格式来适应客户端要求app

      2.图片经过接口模式给客户端,最终使用base64来传递dom

 

日常思考模式:

     1.BufferedImage首先经过工具把数据生成出来。工具

     2.我绝对不会把这个BufferedImage写磁盘,直接放内存ByteArrayOutputstream后转base64岂不是更快?测试

     3.ImageIO.write正好有个write(BufferedImage img,String format,OutputStream output)优化

     4.真的舒服,我就用它了!this

 

实际状况:

    1.Linux环境centos6.8 虚拟化环境spa

    2.JRE1.8.net

    3.接口工做流程:
(1) 生成BufferedImage
(2) BufferedImage经过

ImageIO.write(BufferedImage,"png",ByteArrayOutputStream out)

(3)将ByteArrayOutputStream转化为base64
(4) 接口返回

    4.一个普通的连接,生成二维码并返回base64,接口耗时1.7S

    5.png图片大小16K

 

分析问题&尝试更换接口:

     1.一个图片生成16K,不大

     2.一次请求1.7s,又是手机端应用,太慢了!不能接受

     3.根据代码跟踪分析得出速度慢在 ImageIO.write这里

     4.网上搜索信息也有相关的反馈说ImageIO.write png的时候奇慢无比,可是没有找到实际解决方法

     5.尝试更换write的ByteArrayOutputStream为File,由于 ImageIO.write正好支持写文件ImageIO.write(BufferedImage,"png",File out)

     6.测试结果:write到file后,接口响应时间在400ms!!!

 

查看源代码:

     1.对比write到Byte和File的源代码发现,使用ByteArrayOutputStream的底层写数据的时候使用了FileCacheImageOutputStream,而使用File的底层写数据的时候使用了FileImageOutputStream。

     2.查看FileCacheImageOutputStream的初始化方式、和写数据相关代码

//初始化代码
public FileCacheImageOutputStream(OutputStream stream, File cacheDir) throws IOException { if (stream == null) { throw new IllegalArgumentException("stream == null!"); } if ((cacheDir != null) && !(cacheDir.isDirectory())) { throw new IllegalArgumentException("Not a directory!"); } this.stream = stream; //这里居然建立了临时文件
        if (cacheDir == null) this.cacheFile = Files.createTempFile("imageio", ".tmp").toFile(); else
            this.cacheFile = Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp") .toFile(); this.cache = new RandomAccessFile(cacheFile, "rw"); this.closeAction = StreamCloser.createCloseAction(this); StreamCloser.addToQueue(closeAction); } // 写数据,没什么特殊
 public void write(int b) throws IOException { flushBits(); // this will call checkClosed() for us
 cache.write(b); ++streamPos; maxStreamPos = Math.max(maxStreamPos, streamPos); } //关闭
public void close() throws IOException { maxStreamPos = cache.length(); seek(maxStreamPos); //注意这里!!!!!
 flushBefore(maxStreamPos); super.close(); cache.close(); cache = null; cacheFile.delete(); cacheFile = null; stream.flush(); stream = null; StreamCloser.removeFromQueue(closeAction); } //把数据写入ByteArrayOutputStream
public void flushBefore(long pos) throws IOException { long oFlushedPos = flushedPos; super.flushBefore(pos); // this will call checkClosed() for us

        long flushBytes = flushedPos - oFlushedPos; if (flushBytes > 0) { // 这里使用了一个逻辑每次只读512个字节到stream里面!!而后循环
            int bufLen = 512; byte[] buf = new byte[bufLen]; cache.seek(oFlushedPos); while (flushBytes > 0) { int len = (int)Math.min(flushBytes, bufLen); cache.readFully(buf, 0, len); stream.write(buf, 0, len); flushBytes -= len; } stream.flush(); } }

 

      3.而FileImageOutputStream 的相关代码以下,都很中规中矩没有什么特殊

//初始化
public FileImageOutputStream(File f) throws FileNotFoundException, IOException { this(f == null ? null : new RandomAccessFile(f, "rw")); } //写数据
public void write(int b) throws IOException { flushBits(); // this will call checkClosed() for us
 raf.write(b); ++streamPos; } //关闭
 public void close() throws IOException { super.close(); disposerRecord.dispose(); // this closes the RandomAccessFile
        raf = null; }

 

分析源代码:

      1.使用了cache的方式对数据读取和写入作了优化,为了防止内存溢出他已512字节读取而后写入输出流。可是当写到ByteArrayOutputStream的时候反而显得笨拙,一个16k的图片走cache的方式须要反复读取32次。

      2.使用了普通模式的读取写入数据中规中矩,而读取由于了解文件大小都在16k左右,我采用了一次性读取到内存,因此将File类型的读取到内存再转化base64的时候,只发生了1次磁盘IO

 

结论:

    1. 咱们不能被代码外表所欺骗,乍一眼以为写内存确定比写File要快。

    2.FileCacheImageOutputStream的出发点是好的,分批次数据读取而后写输出流

    3.ImageIO.write 下面这出代码针对ByteArrayOutputStream的策略选择有失误:

while (iter.hasNext()) { ImageOutputStreamSpi spi = (ImageOutputStreamSpi)iter.next(); if (spi.getOutputClass().isInstance(output)) { try { //针对ByteArrayOutputStream输出流选择 ImageOutputStream的实现不理想
                    return spi.createOutputStreamInstance(output, usecache, getCacheDirectory()); } catch (IOException e) { throw new IIOException("Can't create cache file!", e); } } }

     磁盘缓存对ByteArray输出流没有效果,该溢出的仍是会溢出,还不如直接不使用cache

     4.最终咱们采用了先写图片到磁盘文件,而后读取文件转base64再返回,接口稳定在了 400ms内

https://my.oschina.net/u/2461727/blog/3024892?from=timeline&isappinstalled=0

相关文章
相关标签/搜索