日常工作采坑,关于图片压缩哪些坑一次性踩完。

文章目录

  • 0.前言
  • 1.代码实现
  • 2.压缩工具包的配置

0.前言

  首先说明一下这个图片压缩为什么那么艰难,主要原因还是在于需求过于奇葩。比较奇葩的原因有如下几点:
  1.图片是一个很大的文件,我长这么大还没见过这个大的文件。图下可以图片文件可以直奔100Mb了。其实还真有超过100Mb的图片,就不一一列举了。
  在这里插入图片描述
  在这里插入图片描述
  2.第二个原因,图片大小不做限制。
  3.由于压缩图片执行比较慢,在还没执行完的时候就已经有别的线程进行压缩了,特别是压缩的文件时可能消耗30s左右,此时其他用户线程也进入到压缩程序中执行,严重是还导致了内存溢出.。
  解决问题的前置知识,可查看往期文章:
  手写redis实现分布式锁详细教程,满足可续锁、可重入等分布式锁条件
  Redisson分布式锁分析,可重入、可续锁(看门狗)

1.代码实现

  首先为了避免同一个文件多次压缩,导致内存溢出问题,我们要确保一个大文件一天内只能压缩一次,而且压缩后的结果会放到本地磁盘暂存,避免反复压缩。
  大文件一天内只能压缩一次如何实现呢?这将是这次问题解决方案的重中之重。加锁分布式锁,使用双检加锁方法,就在获取锁以后还要在判断本地磁盘上是否已经存有已经压缩的图片。
  由此避免大图片文件多次重复压缩造成了内存溢出。但是文中的分布式锁并不是完善的,因为系统并发量并不是很高,所以做了一个简易的分布式锁。

  public void getCompressImage(HttpServletResponse response, String attachId) throws IOException {if (StringUtils.isBlank(attachId)){log.info("附件id不能为空" );return;}String compressKey = RedisKeyConsts.CACHE_KEY_COMPRESS_BYTE + attachId;byte[] compressbyte = null;String filePath = "";filePath = (String) redisTemplate.opsForValue().get(compressKey);if (StringUtils.isNotBlank(filePath)){File fileCache = new File(filePath);if (fileCache.exists()){compressbyte = attachInfoService.convertFileToByteArray(fileCache);}}Attachment attach = this.attachmentService.getById(attachId);String suffix = FileUtil.getSuffix(attach.getFileName());if (compressbyte == null || compressbyte.length == 0) {AttachmentVO attachmentVO = new AttachmentVO();BeanUtil.copyProperties(attach, attachmentVO);byte[] fileBytes = null ;try {fileBytes = this.attachmentService.download(attachmentVO);}catch (Exception e){e.printStackTrace();}if (fileBytes == null){return;}String lockKey = "LOCK::"+compressKey;//自旋加锁while (!redisTemplate.opsForValue().setIfAbsent(lockKey , "1"  , 70 , TimeUnit.SECONDS)){try {Thread.sleep(30);} catch (InterruptedException e) {e.printStackTrace();}}//如果已经压缩过了,就不用再压缩了,直接返回压缩好的文件直接取数filePath = (String) redisTemplate.opsForValue().get(compressKey);if (StringUtils.isNotBlank(filePath)){File fileCache = new File(filePath);if (fileCache.exists()){compressbyte = attachInfoService.convertFileToByteArray(fileCache);}}if (compressbyte == null || compressbyte.length == 0){long time1 = System.currentTimeMillis();//压缩文件String tempPath = com.haday.tp.attachment.core.util.FileUtil.concatPath(System.getProperty("java.io.tmpdir"), AttachConstants.TEMP_PATH, IdUtil.fastSimpleUUID(), attach.getFileName());File file = FileUtil.writeBytes(fileBytes, tempPath);CompressUtils.doWithPhoto(tempPath  , fileBytes , 10 );long time2 = System.currentTimeMillis();log.info("文件压缩时间" + (time2 - time1) + "ms");redisTemplate.opsForValue().set(compressKey,tempPath );//转成字节型compressbyte = attachInfoService.convertFileToByteArray(file);//压缩时间没有确定时间,需要手动删除redisTemplate.delete(lockKey);}}response.setContentType("image/" + suffix);response.getOutputStream().write(compressbyte);response.getOutputStream().close();}

  执行图片的压缩,压缩图片使用到com.sun.image.codec.jpeg包,java8以后变成了私有包了,所以要使用这个必须做相应的配置,下面会给出详细的配置。

package com.haday.media.utils;import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import org.jcp.xml.dsig.internal.dom.Utils;import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;
public class CompressUtils {/*** 对图片进行原比例无损压缩,压缩后覆盖原图片*** @param path* @param rate  压缩比例*/public static void doWithPhoto(String path ,byte [] filebyte ,  int rate) {BufferedImage image = null;OutputStream os = null;try {image = readMemoryImage(filebyte);int width = image.getWidth() / rate  ;int height = image.getHeight() / rate  ;BufferedImage bfImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);bfImage.getGraphics().drawImage(image.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);os = new FileOutputStream(path);JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(os);encoder.encode(bfImage);} catch (IOException e) {e.printStackTrace();} finally {if (os != null) {try {os.close();} catch (IOException e) {e.printStackTrace();}}}/*** 获取文件流* @param path* @return*/public static InputStream getUrlFile(String path){try {File file = new File(path);InputStream inputStream = new FileInputStream(file);return inputStream;} catch (Exception e) {e.printStackTrace();}return null;}public static final byte[] readBytes(InputStream in) throws IOException {if (null == in){throw new NullPointerException("the argument 'in' must not be null");}try {int buffSize = Math.max(in.available(), 1024 * 8);byte[] temp = new byte[buffSize];ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize);int size = 0;while ((size = in.read(temp)) != -1) {out.write(temp, 0, size);}return out.toByteArray();} finally {in.close();}}/*** 从内存字节数组中读取图像** @param imgBytes*            未解码的图像数据* @return 返回 {@link BufferedImage}* @throws IOException*             当读写错误或不识别的格式时抛出*/public static final BufferedImage readMemoryImage(byte[] imgBytes) throws IOException {if (null == imgBytes || 0 == imgBytes.length){throw new NullPointerException("the argument 'imgBytes' must not be null or empty");}// 将字节数组转为InputStream,再转为MemoryCacheImageInputStreamImageInputStream imageInputstream = new MemoryCacheImageInputStream(new ByteArrayInputStream(imgBytes));try {// 获取所有能识别数据流格式的ImageReader对象Iterator<ImageReader> it = ImageIO.getImageReaders(imageInputstream);// 迭代器遍历尝试用ImageReader对象进行解码while (it.hasNext()) {ImageReader imageReader = it.next();// 设置解码器的输入流imageReader.setInput(imageInputstream, true, true);// 图像文件格式后缀String suffix = imageReader.getFormatName().trim().toLowerCase();// 图像宽度int width = imageReader.getWidth(0);// 图像高度int height = imageReader.getHeight(0);System.out.printf("format %s,%dx%d\n", suffix, width, height);try {// 解码成功返回BufferedImage对象// 0即为对第0张图像解码(gif格式会有多张图像),前面获取宽度高度的方法中的参数0也是同样的意思return imageReader.read(0, imageReader.getDefaultReadParam());} catch (Exception e) {imageReader.dispose();// 如果解码失败尝试用下一个ImageReader解码}}}catch (Exception e){e.printStackTrace();}finally {imageInputstream.close();}// 没有能识别此数据的图像ImageReader对象,抛出异常throw new IOException("unsupported image format");}
}

2.压缩工具包的配置

如果不进行下面配置,项目打包将会报错:程序包com.sun.image.codec.jpeg不存在。下面的的配置正是解决改问题的方法。

 <build><plugins><!-- 指定JDK编译版本 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding><compilerArguments><verbose /><bootclasspath>${JAVA_HOME}/jre/lib/rt.jar${path.separator}${JAVA_HOME}/jre/lib/jce.jar</bootclasspath></compilerArguments></configuration></plugin></plugins></build><!-- 环境 --><profiles><!-- 开发 --><profile><id>dev</id><activation><!--默认激活配置--><activeByDefault>true</activeByDefault></activation><properties><!--当前环境--><profile.name>dev</profile.name></properties></profile><!-- 测试 --><profile><id>test</id><activation><!--默认激活配置--><activeByDefault>false</activeByDefault></activation><properties><!--当前环境--><profile.name>test</profile.name><JAVA_HOME>/data/usr/repo/jdk1.8.0_202</JAVA_HOME></properties></profile><!-- 生产 --><profile><id>prod</id><properties><!--当前环境,生产环境为空--><profile.name>prod</profile.name><JAVA_HOME>/usr/nhip/jdk1.8.0_202</JAVA_HOME></properties></profile></profiles>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/59438.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

语音识别ic赋能烤箱,离线对话操控,引领智能厨房新体验

一、智能烤箱产品的行业背景 随着科技的飞速发展&#xff0c;智能家居已经成为现代家庭的新宠。智能烤箱作为智能家居的重要组成部分&#xff0c;正逐渐从高端市场走向普通家庭。消费者对于烤箱的需求不再仅仅局限于基本的烘焙功能&#xff0c;而是更加注重其智能化、便捷化和…

一文详解开源ETL工具Kettle!

一、Kettle 是什么 Kettle 是一款开源的 ETL&#xff08;Extract - Transform - Load&#xff09;工具&#xff0c;用于数据抽取、转换和加载。它提供了一个可视化的设计环境&#xff0c;允许用户通过简单的拖拽和配置操作来构建复杂的数据处理工作流&#xff0c;能够处理各种数…

D59【python 接口自动化学习】- python基础之异常

day59 捕获异常常见问题 学习日期&#xff1a;20241105 学习目标&#xff1a;异常 -- 75 避坑指南&#xff1a;编写捕获异常程序时经常出现的问题 学习笔记&#xff1a; 捕获位置设置不当 设置范围不当 捕获处理设置不当 嵌套try-except语法错误 总结 位置&#xff0c;范围…

Java开发配置文件的详情教程配置文件类型

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

应审稿人要求| pseudo bulk差异分析

一、写在前面 最近有粉丝提问&#xff0c;收到了如下的审稿人意见&#xff1a; 审稿人认为在单细胞测序过程中&#xff0c;利用findMarker通过Wilcox获得的差异基因虽然考虑到了不同组别细胞数量的不同&#xff0c;但是未能考虑到每组样本数量的不同。因此作者希望纳入样本水平…

Android13 系统/用户证书安装相关分析总结(二) 如何增加一个安装系统证书的接口

一、前言 接着上回说&#xff0c;最初是为了写一个SDK的接口&#xff0c;需求大致是增加证书安装卸载的接口&#xff08;系统、用户&#xff09;。于是了解了一下证书相关的处理逻辑&#xff0c;在了解了功能和流程之后&#xff0c;发现settings中支持安装的证书&#xff0c;只…

矩阵特殊打印方式

小伙伴们大家好&#xff0c;好几天没更新了&#xff0c;主要有个比赛。从今天起继续给大家更新&#xff0c;今天给大家带来一种新的题型&#xff1a;矩阵特殊打印方式。 螺旋打印矩阵 解题思路 首先给大家看一下什么是螺旋方式打印&#xff1a; 就像这样一直转圈圈。 我想大多…

C语言 流程控制语句

时间&#xff1a;2024.11.5 一、学习内容 流程控制语句&#xff1a; 通过一些语句&#xff0c;控制程序的执行流程。 1、顺序结构 从上往下依次执行&#xff0c;是程序默认的执行过程。 2、if的第一种格式 if(关系表达式) { 语句体&#xff1b; } //考试奖励&#xff1a;…

03集合基础

目录 1.集合 Collection Map 常用集合 List 接口及其实现 Set 接口及其实现 Map 接口及其实现 Queue 接口及其实现 Deque 接口及其实现 Stack类 并发集合类 工具类 2.ArrayList 3.LinkedList 单向链表的实现 1. 节点类&#xff08;Node&#xff09; 2. 链表类&a…

HTMLCSS:3D 旋转卡片的炫酷动画

效果演示 这段代码是一个HTML和CSS的组合&#xff0c;用于创建一个具有3D效果的动画卡片。 HTML <div class"obj"><div class"objchild"><span class"inn6"><h3 class"text">我是谁&#xff1f;我在那<…

网络自动化03:简单解释send_config_set方法并举例

目录 拓扑图设备信息 netmiko涉及方法send_config_set()方法的简单示例代码输出结果代码解释导入模块配置信息config_device_interface_description 函数主程序块总结 send_config_set方法参数&#xff1a;1. enter_config_mode2. config_commands3. enter_config_mode4. error…

什么是实验室信息(lis)系统?

医院LIS系统定义&#xff1a; 医院LIS系统&#xff0c;即实验室信息系统&#xff08;Laboratory Information System&#xff09;&#xff0c;是专为医院检验科设计的信息管理系统。它通过计算机网络技术实现实验仪器与计算机的联网&#xff0c;智能化、自动化地管理病人样品登…

MySQL45讲 第十六讲 “order by”是怎么工作的?

文章目录 MySQL45讲 第十六讲 “order by”是怎么工作的&#xff1f;一、引言二、全字段排序&#xff08;一&#xff09;索引创建与执行情况分析&#xff08;二&#xff09;执行流程&#xff08;三&#xff09;查看是否使用临时文件 三、rowid 排序&#xff08;一&#xff09;参…

网页版五子棋—— WebSocket 协议

目录 前言 一、背景介绍 二、原理解析 1.连接过程&#xff08;握手&#xff09; 2.报文格式 三、代码示例 1.服务端代码 &#xff08;1&#xff09;TestAPI 类 &#xff08;2&#xff09;WebSocketConfig 类 2.客户端代码 3.代码演示 结尾 前言 从本篇文章开始&am…

【综合案例】使用React编写B站评论案例

一、效果展示 默认效果&#xff0c;一开始默认按照最热进行排序 发布了一条评论 按照最新进行排序 按照最新进行排序 二、效果说明 页面上默认有3条评论&#xff0c;且一开始进入页面的时候是按照点赞数量进行倒序排列展示&#xff0c;可以点击【最热 、最新】进行排序的切换。…

docker镜像文件导出导入

1. 导出容器&#xff08;包含内部服务&#xff09;为镜像文件&#xff08;docker commit方法&#xff09; 原理&#xff1a;docker commit命令允许你将一个容器的当前状态保存为一个新的镜像。这个新镜像将包含容器内所有的文件系统更改&#xff0c;包括安装的软件、配置文件等…

区块链技术与应用-PKU 学习笔记

课程地址 资料&#xff1a; ETH-Security 区块链学习记录_比特币 BTC 密码学原理 比特币&#xff0c;又称加密货币(crypto-currency)&#xff0c;它主要利用了密码学中的哈希函数(cryptographic hash function)的抗碰撞特性(collision resistance)和单向散列特性(hiding) …

在Java中,实现数据库连接通常使用JDBC

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

【优选算法 — 双指针】双指针小专题

和为 s 的两个数 和为s的两个数 题目描述 解法一&#xff1a;暴力枚举 暴力枚举&#xff0c;先固定一个数&#xff0c;然后让这个数和另一个数匹配相加&#xff0c; 如果当前的数 所有剩余的数 target&#xff0c;则返回这两个数&#xff0c;否则固定下一个数&#…

并查集(基础学习与应用)

并查集 基本原理&#xff1a; 对于多个集合&#xff0c;每个集合中的多个元素用一颗树的形式表示&#xff0c;根节点的编号即为整个集合的编号&#xff0c;每个树上节点存储其父节点&#xff0c;使得当前集合的每个子节点都可以通过对父节点的询问来找到根节点&#xff0c;根…