Poi导出Excle

场景

准备金系统需要从数据库读取大量数据存放到List集合中(可能还会做逻辑上的处理),并生成一个Excle文件,下载到客户本地。

问题一:客户体验

如果导出的文件比较大,比如几十万条数据,同步导出页面就会卡主,用户无法进行其他操作。

问题二:服务性能

导出的时候,任务比较耗时就会阻塞主线程。如果导出的服务是暴露给外部(前后端分离),这种大量的数据传输十分消耗性能。

解决方案:使用异常处理导出请求,后台 MQ 通知自己进行处理。MQ 消费之后,多线程处理 excel 文件导出,生成文件后上传到 FTP 等文件服务器。前端直接查询并且展现对应的任务执行列表,去 FTP 等文件服务器下载文件即可。现在准备金系统的数据量在五万条以下,采用的方式是直接查询数据并生成导出Excel文件。这种方式在以后数据量增大以后是有很大问题的。具体问题如下:

问题三:FULL GC

如果一次查询 100W 条数据库,然后把这些信息全部加载到内存中,是不可取的。
建议有2个:
限制每一次分页的数量。比如一次最多查询 1w 条。分成 100 次查询。(必须)
限制查询得总条数。比如限制为最多 10W 条。(根据实际情况选择)
尽量避免 FULL-GC 的情况发生,因为目前的所有方式对于 excel 的输出流都会占用内存,100W 条很容易导致 FULL-GC。

问题四:数据库的压力

去数据库读取的时候一定要记得分页,免得给数据库太大的压力。
一次读取太多,也会导致内存直线上升。比如 100W 条数据,则分成 100 次去数据库读取。

问题五:网络传输

传统的 excel 导出,都是前端一个请求,直接 HTTP 同步返回。导出 100W 条,就在那里傻等。这客户体验不友好,而且网络传输,系统占用多种问题。

建议使用异步处理的方式,将文件上传到文件服务器。前端直接去文件服务器读取。

poi创建Excel文件的三种方式:

1. HSSFWorkbook(excel 2003)
HSSFWorkbook 针对是 EXCEL2003 版本,扩展名为 .xls;所以 此种的局限就是导出的行数 至多为 65535 行,此种 因为行数不够多所以一般不会发生OOM。 

2.  XSSFWorkbook (excel 2007)
这种形式的出现 是由于 第一种HSSFWorkbook 的局限性而产生的,因为其所导出的行数比较少,所以 XSSFWookbook应运而生 其 对应的是EXCEL2007+(1048576行,16384列)扩展名 .xlsx,最多可以 导出 104 万行,不过 这样 就伴随着一个问题---OOM 内存溢出,原因是你所创建的 book sheet row cell 等此时是存在内存的并没有 持久化,那么随着 数据量增大内存的需求量也就增大,那么很大可能就是要 OOM了。

3. SXSSFWorkbook(excel 2007后,poi使用3.8+版本)

因为数据量过大 导致内存吃不消 那么 可以 让内存 到量持久化 吗? 
答案是 肯定的,

此种的情况 就是 设置 最大内存条数比如设置最大内存量为5000 rows  --new SXSSFWookbook(5000),此时当行数 达到 5000 时,把 内存 持久化 写到 文件中,以此 逐步 写入  避免OOM,那么这样 就完美解决了大数据下导出的问题;

 

使用poi导出Excel遇到的问题

1、Excel读写时候内存溢出
虽然POI是目前使用最多的用来做excel解析的框架,但这个框架并不那么完美。大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。

2、OOM

正常的 poi 在处理比较大的 excel 的时候,会出现内存溢出。网上的解决方案也比较多。
比如官方的 SXSSF (Since POI 3.8 beta3) (http://poi.apache.org/components/spreadsheet/index.html)解决方式。
或者使用封装好的包
easypoi ExcelBatchExportServer(http://easypoi.mydoc.io/#text_202984
hutool BigExcelWriter(https://www.hutool.cn/docs/#/poi/Excel%E5%A4%A7%E6%95%B0%E6%8D%AE%E7%94%9F%E6%88%90-BigExcelWriter
原理都是强制使用 xssf 版本的Excel。

hutool在表头的处理方面没法很方便的统一。你可以自己定义类似于 easypoi/easyexcel 中的注解,自己反射解析。然后统一处理表头即可。
你也可以使用 easyexcel(https://github.com/alibaba/easyexcel),当然这个注释文档有些欠缺,而且设计的比较复杂,不是很推荐。

3、关于SXSSFWorkBook

写有大量数据的xlsx文件时,POI为我们提供了SXSSFWorkBook类来处理,这个类的处理机制是当内存中的数据条数达到一个极限数量的时候就flush这部分数据,再依次处理余下的数据,这个在大多数场景能够满足需求。 读有大量数据的文件时,使用WorkBook处理就不行了,因为POI对文件是先将文件中的cell读入内存,生成一个树的结构(针对Excel中的每个sheet,使用TreeMap存储sheet中的行)。如果数据量比较大,则同样会产生java.lang.OutOfMemoryError: Java heap space错误。POI官方推荐使用“XSSF and SAX(event API)”方式来解决。

参考连接:https://houbb.github.io/2018/11/07/excel-export

问题案例

百度的案例(其他人项目遇到的一个问题,自己在写Excel导出工具时可以借鉴,不再出现该问题):链接:https://www.jianshu.com/p/dbb05971ca56

最近公司一个06年统计项目在导出Excel时造成应用服务器内存溢出、假死现象;查看代码发现问题一次查询一整年的数据导致堆内存被撑爆
假死,随后改用批量查询往Excel中写数据,同样的问题又出现了!!!随后在网上查阅了部分资料只是在POI大数据导出API的基础上写的demo示例无任何参考价值...
解决内存溢出常用方法就是打开GC日志: {Heap before GC invocations
=29 (full 14): par new generation total 306688K, used 306687K [0x0000000080000000, 0x0000000094cc0000, 0x0000000094cc0000) eden space 272640K, 100% used [0x0000000080000000, 0x0000000090a40000, 0x0000000090a40000) from space 34048K, 99% used [0x0000000090a40000, 0x0000000092b7ffe0, 0x0000000092b80000) to space 34048K, 0% used [0x0000000092b80000, 0x0000000092b80000, 0x0000000094cc0000) concurrent mark-sweep generation total 1756416K, used 1756415K [0x0000000094cc0000, 0x0000000100000000, 0x0000000100000000) Metaspace used 43496K, capacity 44680K, committed 45056K, reserved 1089536K class space used 5254K, capacity 5515K, committed 5632K, reserved 1048576K 2017-09-12T21:55:02.954+0800: 239.209: [Full GC (Allocation Failure) 2017-09-12T21:55:02.954+0800: 239.209: [CMS: 1756415K->1756415K(1756416K), 5.4136680 secs] 2063103K->1971243K(2063104K), [Metaspace: 43496K->43496K(1089536K)], 5.4138690 secs] [Times: user=5.41 sys=0.00, real=5.41 secs] Heap after GC invocations=30 (full 15): par new generation total 306688K, used 214827K [0x0000000080000000, 0x0000000094cc0000, 0x0000000094cc0000) eden space 272640K, 78% used [0x0000000080000000, 0x000000008d1cacb0, 0x0000000090a40000) from space 34048K, 0% used [0x0000000090a40000, 0x0000000090a40000, 0x0000000092b80000) to space 34048K, 0% used [0x0000000092b80000, 0x0000000092b80000, 0x0000000094cc0000) concurrent mark-sweep generation total 1756416K, used 1756415K [0x0000000094cc0000, 0x0000000100000000, 0x0000000100000000) Metaspace used 43238K, capacity 44256K, committed 45056K, reserved 1089536K class space used 5213K, capacity 5441K, committed 5632K, reserved 1048576K }
主要信息:
2017-09-12T21:55:02.954+0800: 239.209: [Full GC (Allocation Failure) 2017-09-12T21:55:02.954+0800: 239.209: <span style="color:red;">[CMS: 1756415K->1756415K(1756416K), 5.4136680 secs] 2063103K->1971243K(2063104K), [Metaspace: 43496K->43496K(1089536K)], 5.4138690 secs]</span> [Times: user=5.41 sys=0.00, real=5.41 secs] 通过查看GC日志发现堆空间、元空间不能被回收(对象强引用导致) 解决方法: 查看业务代码: SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(1000); for(int i=1;i<=pageCount;i++){ int tableNum = i; int pageIndex = i; //分页数据查询 List<Map<String, Object>> maps = dbFactory.getJdbcTemplate().queryForList(finalSql,(pageIndex-1)*pageSize,pageIndex*pageSize); SXSSFSheet sheet = sxssfWorkbook.createSheet("sheet"+tableNum); SXSSFRow sxssfRow = sheet.createRow(0); for(int a=0;a<titles.length;a++){ sxssfRow.createCell(a).setCellValue(titles[a]); } for(int a=1;a<=maps.size();a++){ SXSSFRow sxssfRow = sheet.createRow(a); Map<String,Object> data = maps.get(a-1); Set<String> keySet = data.keySet(); Iterator<String> iterator = keySet.iterator(); int cell = 0; while(iterator.hasNext()){ String key = iterator.next(); Object valueObject = data.get(key); SXSSFCell sxssfCell = sxssfRow.createCell(cell); sxssfCell.setCellValue(valueObject==null?"":valueObject.toString()); cell++; } } //数据清理 maps.clear(); //设置空引用 maps = null; } FileOutputStream fos = new FileOutputStream(tempPath+fileName); sxssfWorkbook.write(fos); fos.close(); sxssfWorkbook.dispose(); 代码中数据清理、设置空引用都做了,为什么还是不能被回收呢??? 通过JVM自带检测工具jmap查看活跃对象 重大发现原来是
org.apache.poi.xssf.streaming.SXSSFCell、
org.apache.poi.xssf.streaming.SXSSFCell$PlainStringValue、
org.apache.poi.xssf.streaming.SXSSFRow
这三个鬼把内存占完了 优化代码 SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(1000); SXSSFCell sxssfCell = null; SXSSFRow sxssfRow = null; for(int i=1;i<=pageCount;i++){ int tableNum = i; int pageIndex = i; List<Map<String, Object>> maps = dbFactory.getJdbcTemplate().queryForList(finalSql,(pageIndex-1)*pageSize,pageIndex*pageSize); SXSSFSheet sheet = sxssfWorkbook.createSheet("sheet"+tableNum); sxssfRow = sheet.createRow(0); for(int a=0;a<titles.length;a++){ sxssfRow.createCell(a).setCellValue(titles[a]); } for(int a=1;a<=maps.size();a++){ sxssfRow = sheet.createRow(a); Map<String,Object> data = maps.get(a-1); Set<String> keySet = data.keySet(); Iterator<String> iterator = keySet.iterator(); int cell = 0; while(iterator.hasNext()){ String key = iterator.next(); Object valueObject = data.get(key); sxssfCell = sxssfRow.createCell(cell); sxssfCell.setCellValue(valueObject==null?"":valueObject.toString()); cell++; } //map数据清理 data.clear(); } //数据清理 maps.clear(); //设置空引用 maps = null; } FileOutputStream fos = new FileOutputStream(tempPath+fileName); sxssfWorkbook.write(fos); fos.close(); sxssfWorkbook.dispose(); 程序SXSSFRow、SXSSFCell这两个对象持有一个引用,每当新创建一个对象时候原来引用失效jvm会自动回收

通过上述案例,自己在编写Excel导出工具时,注意也可以使上述的两个对象都持有一个引用。

自己的工具(附代码):

Excel的简单介绍

.xls 是03版Office Microsoft Office Excel 工作表的格式,用03版Office,新建Excel默认保存的Excel文件格式的后缀是.xls;

.xlsx 是07版Office Microsoft Office Excel 工作表的格式,用07版Office,新建Excel默认保存的的Excel文件格式后缀是.xlsx。

07版的Office Excel,能打开编辑07版(后缀.xlsx)的Excel文件,也能打开编辑03版(后缀.xls)的Excel文件,都不会出现乱码或者卡死的情况。

但是,03版的Office Excel,就只能打开编辑03版(后缀.xls)的Excel文件;如果打开编辑07版(后缀.xlsx)的Excel文件,则可能出现乱码或者开始能操作到最后就卡死,以后一打开就卡死。

那么07版.xlsx的Excel文件,怎么才能在03版的Office Excel中打开呢?

也简单,举个例,你家里的电脑用的07版Office Excel,你在家里做好一个Excel的文件,你默认保存的话就是.xlsx格式;如果你要拷到公司电脑上用,公司的电脑是03版Office Excel,要是你直接拷过去的话,是没法用的;你得这样,在家里做Office Excel的时候,保存的时候,点击office按钮,采用另存为“97-2003 Excel 工作簿”,这样保存的格式就是03版的.xls格式,这样就实现.xlsx文件转换成.xls。这样再把.xls格式Excel文件拷到公司的电脑上就能用了,就OK了。

如果你家里的电脑是03版Office Excel,那么默认保存(.xls)就行,不管公司的电脑是03版的还是07版的Office。

EXCEL 的限制
EXCEL 的上限行数为 1048576。2^20。超过这个数量,EXCEL 打开会有问题。

猜你喜欢

转载自www.cnblogs.com/vole/p/12089994.html