本文主要是介绍Poi版本升级优化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Poi-3.17前后版本api使用差异
1. 升级缘由
最近公司prod环境出现因为Excel文件下载数据量过大导致应用out of memory, 然后就需要找到内存溢出的原因及优化方案. 经分析, 得出以下结论:
1.1 事故原因
(1) 应用场景发生在页面列表查询功能, 可以批量勾选列表项选择下载, 后台又需要根据勾选的列表项字段关联查询出
N+条大量明细数据, 系统程序又没有对大数据量做评估校验, 一路绿灯,最终形成社会性死亡现场 . 导致宕机的主要原因倒不是因为单个请求线程的数据量过大导致, 当前请求最多不过是下载失败, 真正导致应用宕机是因为前端界面一直展示下载加载效果, 用户一直重复点击, 多个请求线程访问应用, 熟悉POI的老铁们应该都知道, POI生成DOM节点需要消耗大量的内存, 结果就是开头的out of memory了.(2) 系统监控平台发现大量接口请求超时, 为了不影响后续其他业务的正常作业, 立即展开抢救, 过程就是根据导致宕机的请求数据找到操作用户(因为是内网用户, 可以查到用户联系方式), 然后打电话给那位用户说明情况并让其不要再重复点击, 最后就是重启
Server了, 很多问题都是重启可以搞定的哈, 当然我们是主从2台机器, 所以停机重启不会大面积影响业务, 请求会经F5负载均衡转发到另外一台正常运行的机器上去 .(3) 经过在测试环境复现事故场景, 发现超过65536条(含标题行)记录就会报错, 相信用过
Poi的老铁们都知道了, 我们程序使用的是POI的HSSF创建的Workbook工作簿, 也就是创建的2003版Excel文件(xls), 最多仅支持65536行记录写入(0-65535), 超过这个量的数据下载肯定会报错了. 下面是我自己测试复现的报错截图:
1.2 优化方案
分析出上面的问题原因后, 我也查阅了操作Excel相关的技术, 尝试了下面几种解决方案.
1.2.1 改用XSSF生成
针对上面HSSF只能写入65536行记录的局限, POI也是给出了解决方案的, XSSF创建的Workbook工作簿, 生成的是2007版Excel(xlsx), 支持上限1048576行记录的写入 , 基本满足我们日常的应用场景了. 另外在XSSF基础上, POI-3.8版本开始提供的支持低内存占用的操作方式SXSSFWorkbook, 支持2007以上版本的Excel操作.
关键代码体现 :
/*** 根据要生成的文件类型创建HSSF或者XSSF工作簿* @param fileType .xls .xlsx* @return*/
public static Workbook getWorkbook(String fileType) {Workbook wb = null;switch (fileType) {case CSISCONSTANT.EXCEL03_EXTENSION:wb = new HSSFWorkbook(); // 创建工作簿 2003版excelbreak;case CSISCONSTANT.EXCEL07_EXTENSION:default:wb = new XSSFWorkbook(); // 创建工作簿 2007版excelbreak;}return wb;
}
具体实现代码请移步博客Poi实现Excel导出
1.2.2 数据拆分文件压缩
XSSF方案只是解决了大数据量写入的问题, 从系统安全性和性能方面考虑, 还有更多的优化空间. 程序现状没有对数据做分流处理, 依然是全部数据一次性生成并写入Excel文件, 对内存的消耗仍旧很糟糕. 其实可以对查询出来的大量数据根据自己设置的阈值做分流处理, 阈值设置在1万-2万之间, 因为打开一个Excel文件当前Sheet页方便快速翻阅查看最好了, 数据写入太多翻页查看数据会很卡, 用户体验不好. 根据阈值分流后的数据在性能允许范围内, 循环调用提前封装好的生成Excel文件的方法, 每次循环生成后Excel文件后, 会释放掉POI消耗的内存, 相比较一次性生成并写入Excel文件内存的占用时间和消耗小很多. 待所有Excel文件生成完成, 最后将这些文件打包压缩成zip文件流返回给界面.
另外, SpringMVC中的Web端文件下载是在当前请求线程内完成的, 在生成并写入数据文件的过程中, 界面只能等着后端系统的响应, 且当前请求的线程会一直被占用着, 数据量不大还好, 如果数据量很大会影响用户体验. 我工作中用到的是前后端分离的项目, 文件下载是异步请求实现的, 设计思想就是界面发起文件下载的异步请求, 后台程序将生成的数据文件存放到服务器临时下载目录中 , 将临时下载目录的数据文件路径以流的形式返回给前端界面, 前端使用封装好的api直接去服务器临时下载目录中下载文件即可, 而不需要将整个数据文件以流的形式返回给前端界面. 文件上传也可以采用这种思想, 前端将文件直接上传到服务器临时上传目录中, 将文件路径提交到后台系统, 后台程序直接去服务器临时上传目录中读取解析.
数据分流生成Excel的实现代码请移步博客 数据分流写入Excel
文件压缩的实现代码请移步博客 Poi实现Excel导出
1.2.3 使用开源技术EasyExcel
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百MB的Excel文件. 阿里的大牛们对POI进行了各种封装和优化, 不管是性能还是内存消耗方面都是质的飞跃, 在api使用方面也极具人性化, 简单易用.
说明:
EasyExcel底层是依赖POI的, 最低版本的EasyExcel要求POI-3.17版本, 如果我用这个方案, 必然面临POI版本升级问题, 因为POI-3.17版本相比POI3.17以下的版本, 在样式设置和单元格数据类型设置等方面改动比较大, 很多API进行了抽取. 项目组的系统有很多文件下载功能, 不能因为这个场景的问题全面改造, 没有测试人力的全面支持, 无法形成有效的升级. 后面我采用了数据分流写入并压缩的方案, 避免了POI版本升级的麻烦. 但是作为一名技术宅男, 肯定是不会放过这个优化方案的, 自己业余时间搭建了demo进行了冲突aip的升级改造, 具体冲突api的使用请见 Poi升级 .
EasyExcel请移步博客 EasyExcel学习笔记
1.2.4 其他
除了原生POI, EasyExcel外, 还有其他相关技术可以实现Excel的操作, 比如EasyPoi, Hutool-poi, jxl等.
EasyPoi请移步博客 EasyPoi学习笔记
Hutool-poi请参考Hutool官方文档中的office文档操作部分.
2. Poi升级
POI3.17与POI旧版本对比, 有很多API的使用改变了, 下面将依次列出它们的不同及新API的使用.
2.1 颜色定义
旧版本
HSSFColor.GREEN.index
HSSFColor.BLACK.index
新版本
IndexedColors.GREEN.getIndex()
IndexedColors.GREEN.index
IndexedColors.BLACK.getIndex()
IndexedColors.BLACK.index
2.2 获取单元格格式
旧版本
// 获取单元格格式
int cellType = cell.getCellType();
// 与之对应的单元格格式int值
HSSFCell.CELL_TYPE_BLANK // 空
HSSFCell.CELL_TYPE_STRING // 字符串
HSSFCell.CELL_TYPE_NUMERIC // 数字类型
HSSFCell.CELL_TYPE_BOOLEAN // 布尔
HSSFCell.CELL_TYPE_FORMULA // 公式
HSSFCell.CELL_TYPE_ERROR // 错误
新版本
CellType cellTypeEnum = cell.getCellTypeEnum(); // 获取单元格格式
// 与之对应的单元格格式枚举值
CellType.BLANK // 空
CellType.STRING // 字符串
CellType.NUMERIC // 数字类型
CellType.BOOLEAN // 布尔
CellType.FORMULA // 公式
CellType.ERROR // 错误
2.3 设置单元格数据类型
旧版本
Cell cell = row.getCell(0); // 获取单元格对象
cell.setCellType(Cell.CELL_TYPE_STRING); // 设置单元格为字符串类型
新版本
Cell cell = row.getCell(0); // 获取单元格对象
cell.setCellType(CellType.STRING); // 设置单元格为字符串类型
2.4 设置单元格样式
单元格样式包含垂直居中样式, 边框样式, 背景填充颜色, 边框线条等…
旧版本
HSSFCellStyle cellStyle = wb.createCellStyle();
// XSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setFillForegroundColor(HSSFColor.LIME.index); // 标题行背景色为绿色
cellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); // 填充背景色
cellStyle.setAlignment(XSSFCellStyle.ALIGN_CENTER); // 文字水平居中
cellStyle.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER); // 文字垂直居中
cellStyle.setBorderBottom(XSSFCellStyle.BORDER_THIN); // 底部边框实体线条
cellStyle.setBorderTop(XSSFCellStyle.BORDER_THIN); // 顶部边框实体线条
cellStyle.setBorderLeft(XSSFCellStyle.BORDER_THIN); // 左部边框实体线条
cellStyle.setBorderRight(XSSFCellStyle.BORDER_THIN); // 右部边框实体线条
新版本
CellStyle cellStyle = wb.createCellStyle();
// XSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setFillForegroundColor(IndexedColors.LIME.index); // 标题行背景色为绿色
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 填充背景色
cellStyle.setAlignment(HorizontalAlignment.CENTER); // 文字水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 文字垂直居中
cellStyle.setBorderBottom(BorderStyle.THIN); // 底部边框实体线条
cellStyle.setBorderTop(BorderStyle.THIN); // 顶部边框实体线条
cellStyle.setBorderLeft(BorderStyle.THIN); // 左部边框实体线条
cellStyle.setBorderRight(BorderStyle.THIN); // 右部边框实体线条
2.5 合并单元格
旧版本
Sheet sheet = workbook.createSheet("sheet1");
// 起始行,结束行,起始列,结束列
sheet.addMergedRegion(new CellRangeAddress(1, 1,(short) 0, (short) 0));
新版本
Sheet sheet = workbook.createSheet("sheet1");
// 起始行,起始列,结束行,结束列
sheet.addMergedRegion(new Region(1, (short) 0, 1,(short) 0));
2.6 设置字体加粗
旧版本
Font font = workbook.createFont();
// font.setBoldweight((short) 400);
font.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
font.setBoldweight(XSSFFont.BOLDWEIGHT_NORMAL);
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
新版本
Font font = workbook.createFont();
font.setBold(true);
2.7 设置图片属性
旧版本
// anchor主要用于设置图片的属性
XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 255, 255, (short) i, rowNum + 1,(short) i + 1, rowNum + 2);
anchor.setAnchorType(3);
// 插入图片
patriarch.createPicture(anchor,workbook.addPicture(byteArrayOut.toByteArray(), ClientAnchor.MOVE_DONT_RESIZE));
新版本
// anchor主要用于设置图片的属性
XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 255, 255, (short) i, rowNum + 1,(short) i + 1, rowNum + 2);
anchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE));
// 插入图片
patriarch.createPicture(anchor,workbook.addPicture(byteArrayOut.toByteArray(), ClientAnchor.AnchorType.MOVE_DONT_RESIZE.value));
2.8 小结
poi版本升级问题产生报错汇总(后续待补充)
| poi3.17之前版本 | poi3.17+版本 | 用途 |
|---|---|---|
| Cell.CELL_TYPE_STRING | CellType.STRING | 单元格数据格式判断 |
| HSSFCell.CELL_TYPE_NUMERIC | CellType.NUMERIC | 单元格数据格式判断 |
| CellStyle.ALIGN_CENTER | HorizontalAlignment.CENTER | 单元格水平居中 |
| CellStyle.VERTICAL_CENTER | VerticalAlignment.CENTER | 单元格垂直居中 |
| HSSFColor.GREY_25_PERCENT.index | IndexedColors.GREY_25_PERCENT.index | 设置图案颜色 |
| CellStyle.SOLID_FOREGROUND | FillPatternType.SOLID_FOREGROUND | 设置图案样式 |
| CellStyle.BORDER_THIN | BorderStyle.THIN | 边框 |
| ClientAnchor.MOVE_DONT_RESIZE | AnchorType.MOVE_DONT_RESIZE.value | 单元格插入图片 |
相关推荐
数据分流写入Excel
Poi版本升级优化
StringTemplate实现Excel导出
Poi模板技术
SAX方式实现Excel导入
DOM方式实现Excel导入
Poi实现Excel导出
EasyExcel实现Excel文件导入导出
EasyPoi实现excel文件导入导出
个人博客
欢迎各位访问我的个人博客: https://www.crystalblog.xyz/
备用地址: https://wang-qz.gitee.io/crystal-blog/
这篇关于Poi版本升级优化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!
