土制Excel导入导出及相关问题探讨
程序员文章站
2022-04-15 17:17:34
转载请注明出处 "https://www.cnblogs.com/funnyzpc/p/10392085.html" 新的一年,又一个开始,不见收获,却见年龄,好一个猪年,待我先来一首里尔克的诗: 《沉重的时刻》(里尔克) ok,这次说说项目中经常用到的Excel导出问题,目前就用到的可以操作Exc ......
转载请注明出处
新的一年,又一个开始,不见收获,却见年龄,好一个猪年,待我先来一首里尔克的诗:
《沉重的时刻》(里尔克)
此刻有谁在世上某处哭,无缘无故在世上哭,在哭我。 此刻有谁在夜间某处笑,无缘无故在夜间笑,在笑我。 此刻有谁在世上某处走,无缘无故在世上走,走向我。 此刻有谁在世上某处死,无缘无故在世上死,望着我。
ok,这次说说项目中经常用到的excel导出问题,目前就用到的可以操作excel的技术(在java中)大致有两类:
- jxl
- 仅仅支持对xls的文件读写,
- 仅包含excel基础api,比较老,很久不更新
- 读写速度还行,对于要求低同时兼容性较好的推荐
- poi
- 可支持xls、xlsx两种格式的excel文件读写
- hssf:操作excel 97(.xls)格式
- xssf:操作excel 2007 ooxml (.xlsx)格式,操作excel内存占用高于hssf
- sxssf: 从poi3.8 beta3开始支持,基于xssf,低内存占用。
- 技术较新,保留了最大兼容性,可对excel做复杂对数据极样式处理
- 读写速度上 sxssf快于xssf ,hssf速度同略逊于jxl
- 可支持xls、xlsx两种格式的excel文件读写
以上对于这两种技术做了简要对描述,在开发中,我们一般将poi作为首选,同时以上还可能存在一个问题是:大数据量导出。大数据导出,一般我们需要解决两个问题:
- 大数据量读写容易造成内存不足问题
- 长时读写容易造成客户端请求超时,造成导出失败问题
- 大数据量处理耗时问题
对于以上几个问题,解决思路大致有下:
- 数据库数据查询阶段建议使用fetch分批次查询,减少数据库压力
- 单个文件读写建议使用sxssf,以减少内存占用
- 对于单个sheet超过十万的建议分sheet做多线程写入,这里分享一个网友写的demo
- example:createmultiplesheetdemo.java
- 对于poi写入效率的问题官方给了个demo,这个例子大致是使用xml文档拼接的方式+xml文件压缩的方式
- example:biggriddemo.java
ok,对于以上核心问题个人都尝试过,惮于目前项目进度较为紧张,个人做了个限制导出处理,以避免(客户端请求)超时问题,说白了就是将问题扔给下一期去集中解决。
不考虑以上所说的问题,个人花了些许时间写了两个excel 工具类:excelreadutil以及excelwriteutil (代码地址见篇尾),集中处理了包含单不仅限于以下问题:
- 对象列表("list
- 数据分sheet问题
- 表头字体极单元格内换行问题
- 数据单元格多样式问题
- 数据单元格多数据类型及格式化问题
- 单元格列宽度调整问题
- 大数据拆分问题
- 通用对象导出问题(使用泛型)
当然,解决这些问题也查阅了不少官方资料,同时也做了大量的测试才得以投入正式项目使用,在这中间也跳过一些坑,接下来我就讲一讲我在开发所跳过的坑,这些坑均是相对于上一个版本而言的:
-
使用通用泛型接收参数对象问题
一开始(在上一个项目)做了个初稿,在调用导出方法时直接传入固定类型对象,一开始这样:
toxlsxbymap(list<map<string,object>> datalist, string[] headernames, string[] cellnames, cellfmt[] cellfmts)
导致的问题是传入的datalist内部的map的value必须是object类型,之后钻研了下java泛型,使用这种方式轻松解决问题!
public static <v extends object> file toxlsxbymap(list<map<string,v>> datalist, string[] headernames, string[] cellnames, cellfmt[] cellfmts)
-
分数据分sheet问题
这个问题其实很简单,就是先数据分组,然后循环每组数据时再createsheet,代码片段:
//数据分组 list<list<map<string, v>>> mdata = splitmaplist(datalist); //循环每组数据 并创建sheet>写单元格 for (list<map<string, v>> sublist : mdata) { //第一个sheet 参数(sheet名称,sheet的序号) sheet = workbook.createsheet(string.format("%s~%s", (datalist.size() > data_split_grop_size ? mdata.indexof(sublist) * data_split_grop_size + 1 : 0) + "", (datalist.size() > data_split_grop_size ? (mdata.size() == (mdata.indexof(sublist) + 1) ? datalist.size() : data_split_grop_size * (mdata.indexof(sublist) + 1)) : datalist.size()) + "") ); logger.info(">>>sheet name : {}",sheet.getsheetname()); poicellprocess.writeheadercell(sheet,headercellstyle,headernames); poicellprocess.writebodycellbymap(sheet,bodycellstyle,cellnames,sublist, cellfmts); }
-
单元格内换行问题
其实这是个小问题,只需给cellstyle设置一个setwraptext(true),大致逻辑这样:
public static cellstyle headercellstyle(sxssfworkbook wb){ cellstyle headerstyle = wb.createcellstyle(); //...some code //允许单元格内换行 headerstyle.setwraptext(true); return headerstyle; }
-
单元格类型及格式处理问题
这个问题其实分为多个,而且密切相关,大致有这几个:
- 单元格样式类
- 单元格样式类
- 单元格数据类型
- 单元格写入数据格式但是,处理了这几个问题其实还不够完美
至于不完美的原因是什么呢,一个是excel数据格式与java数据格式不一致(这个体现在日期,长数字,小数的处理上),比如你要格式化的日期后为“yyyy-mm-dd” 这种类型,
但是在excel中相近的格式类型只有这样“yyyy/m/d”,如果强制单元格样式类型为“yyyy-mm-dd hh24:mi:ss” 其实也是可以的,只不过会变成自定义格式,而且是excel的自定义格式,
具体如下图:
另外一个问题是单元格类型与编程语言的数据类型相异同时与poi所能提供的数据类型也相异,如图:
-
列宽调整问题
记得在第一版的时候列宽问题其实并不重要,遂就做个了固定长度
在第二版的时候为了保证可以动态调整列宽,就剔除了第一版的固定长度处理,将长度数据作为一个integer数组传入
由于第二版先期已经投入开发中了,再在方法里面加入长度数组实感觉不合适,于是,想了个用代码做动态列宽,这里实现的思路大致有下:- 由于表头也是作为一个参数传入的,所以将表头字符个数作为字段倍数长度,数据行过长时将表头字段添加适当个数的空格即可(数据传入的时候)
- 实际显示的时候由于存在单元格内换行问题,所以在代码处理的时候先判断换行,所以:
- 有换行时 单元格列宽=基准长度(自己定义的单字符长度)*字符个数/2
- 无换行时 单元格列宽=基准长度(自己定义的单字符长度)*字符个数
这是最终的代码:
public static void writeheadercell(sxssfsheet sheet, cellstyle headercellstyle, string[] headernames) { sxssfrow row = sheet.createrow(0); row.setheight((short) 30); row.setheightinpoints((short) 30); sxssfcell headercell; for (int i = 0; i < headernames.length; i++) { headercell = row.createcell(i); headercell.setcellstyle(headercellstyle); headercell.setcellvalue(headernames[i]); sheet.setcolumnwidth(i, null == headernames[i] ? cell_base_length : (headernames[i].contains("\r\n") ? cell_charset_length * headernames[i].length() / 2 : cell_charset_length * headernames[i].length())); } }
这是最终处理的结果:
-
对象导出问题
这个问题耗时较多,由于我的同事所处理的源数据是这样子 "list - 样例 example
以上写的过于粗糙,各位有更好的想法请分享下哈~
现在是 2019-02-18 星期一,各位中午好~
上一篇: SpringBoot2 配置