我的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
我的ImageIO.write ByteArrayOutputStream为什么这么慢?的更多相关文章
- 从BufferedImage到InputStream,实现绘图后进行下载(生成二维码图片并下载)
@SuppressWarnings("resource") public void download() throws Exception{ String filename = & ...
- Java 二维码生成工具类
/** * 二维码 工具 * * @author Rubekid * */ public class QRcodeUtils { /** * 默认version */ public static fi ...
- java 压缩图片(只缩小体积,不更改图片尺寸)
1.情景展示 在调用腾讯身份证OCR接口的时候,由于要求图片大小只能限制在1MB以内,这样,就必须使用到图片压缩技术 2.代码展示 /** * 图片处理工具类 * @explain * @auth ...
- 在线图片压缩后以ImageIO 流的形式 设置大小显示指定页面
1.Servlet 代码 public class ZoomImgServlet extends HttpServlet implements Servlet { public void init ...
- ImageIO(图像处理)
1.通过ImageIO的read和writer,对图像文件进行处理. BufferedImage buffImage = ImageIO.read(file); // 将图像输出到Servlet输出流 ...
- Java使用imageio、awt生成图片验证码
1.生成验证码工具类 public class CheckCodeTool { private Integer width = 80; private Integer height = 38; pub ...
- 用java imageio调整图片DPI,例如从96调整为300
因项目需求把图片的DPI值提升到300,否则OCR识别产生错乱:直接上源码:1.图片处理接口: package util.image.dpi; import java.awt.image.Buffer ...
- Java 利用 ByteArrayOutputStream 和 ByteArrayInputStream 避免重复读取配置文件
最近参与了github上的一个开源项目 Mycat,是一个mysql的分库分表的中间件.发现其中读取配置文件的代码,存在频繁多次重复打开,读取,关闭的问题,代码写的很初级,稍微看过一些框架源码的人,是 ...
- Java字节流:ByteArrayInputStream ByteArrayOutputStream
----------------------------------------------------------------------------------- ByteArrayInputSt ...
随机推荐
- springboot异步线程
前言 最近项目中出现了一个问题,发现自己的定时器任务在线上没有执行,但是在线下测试时却能执行,最后谷歌到了这篇文章SpringBoot踩坑日记-定时任务不定时了?; 本篇文章主要以自己在项目中遇到的问 ...
- 我瞅瞅源码系列之---flask
快速使用 通过werkzurg 了解wsgi threading.local和高级 LocalStack和Local对象实现栈的管理 Flask源码之:配置加载 Flask源码之:路由加载 ...
- Python解析 算数表达式求值 栈的使用
使用Python实现一种算数表达式求值的算法,模拟这种使用栈的方式,这是由E.W.Dijkstra在20世纪60年代发明的一种非常简单的算法.代码模拟仅仅表现一种编程思想,代码的逻辑并不完全: if ...
- LeetCode runtime error
今天在写LeetCode的某一道题目时候,遇到runtime error问题,本地能过,submit后死活不能通过. 查了一下网上的一些答案,基本上都是数组.指针没有初始化造成野指针.数组索引值越界. ...
- pytest_03_pycharm运行pytest (转:上海悠悠)
前言 上一篇pytest文档2-用例运行规则已经介绍了如何在cmd执行pytest用例,平常我们写代码在pycharm比较多 写完用例之后,需要调试看看,是不是能正常运行,如果每次跑去cmd执行,太麻 ...
- HBase 系列(一)—— HBase 简介
一.Hadoop的局限 HBase 是一个构建在 Hadoop 文件系统之上的面向列的数据库管理系统. 要想明白为什么产生 HBase,就需要先了解一下 Hadoop 存在的限制?Hadoop 可以通 ...
- mongoDB 分组并对分组结果筛选类似于SQL中的(group by xxx having ) 附带Java代码
今天需要做一个筛选程序,因为数据放在mongodb中,没写过分组的查询语句,查了一些资料,终于写出来了,分享给各位小伙伴 需求是 查询 学员 在2019-07-29之后未同步的数据(同一个学员需要2条 ...
- C# DataTable、DataSet、List、相互转换
DataTable转LIst /// <summary> /// 利用反射将DataTable转换为List<T>对象 /// </summary> /// & ...
- K8S 中的容器编排和应用编排
众所周知,Kubernetes 是一个容器编排平台,它有非常丰富的原始的 API 来支持容器编排,但是对于用户来说更加关心的是一个应用的编排,包含多容器和服务的组合,管理它们之间的依赖关系,以及如何管 ...
- activiti用户手册
http://www.mossle.com/docs/activiti/index.html