• 我的ImageIO.write ByteArrayOutputStream为什么这么慢?


    File.createTempFile(prefix, suffix),创建一个临时文件,再使用完之后清理即可。
    但是遇到如下两个坑:

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

    以上代码中,需要注意的两个地方:
    1、prefix必须大于3个字符
    2、suffix需要带上 . , 比如:.png、.zip



    问题来源:

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

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

    平常思考模式:

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

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

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

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

    实际情况:

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

        2.JRE1.8

        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

  • 相关阅读:
    android中listview分页加载数据
    android listview的HeadView左右切换图片(仿新浪,网易,百度等切换图片)
    Opencv cvCircle函数
    我是怎样成长为系统架构师的
    C++游戏编程(一开篇)
    cidaemon.exe进程cpu占用率高及关闭cidaemon.exe进程方法
    curl命令具体解释
    HDU 4334 Trouble
    美国地名大全(美国城市名称英文、中文)
    几种常见模式识别算法整理和总结
  • 原文地址:https://www.cnblogs.com/softidea/p/10811489.html
Copyright © 2020-2023  润新知