poi实现百万级数据导出
注意使用 SXSSFWorkbook 此类在构造表格和处理行高的时候效率极高,刚开始时我使用的 XSSFWorkbook 就出现构造表格效率极低,一万行基本需要3秒左右,那当导出百万级数据就慢的要死啦,而且他会让内存溢出
POI3.8的SXSSF包是XSSF的一个扩展版本,支持流处理,在生成大数据量的电子表格且堆空间有限时使用。SXSSF通过限制内存中可访问的记录行数来实现其低内存利用,当达到限定值时,新一行数据的加入会引起老一行的数据刷新到硬盘。
比如内存中限制行数为100,当行号到达101时,行号为0的记录刷新到硬盘并从内存中删除,当行号到达102时,行号为1的记录刷新到硬盘,并从内存中删除,以此类推。
rowAccessWindowSize代表指定的内存中缓存记录数,默认为100,此值可以通过
new SXSSFWorkbook(int rowAccessWindowSize)或SXSSFSheet.setRandomAccessWindowSize(intwindowSize)来设置。
SXSSF在把内存数据刷新到硬盘时,是把每个SHEET生成一个临时文件,这个临时文件可能会很大,有可以会达到G级别,如果文件的过大对你来说是一个问题,你可以使用下面的方法让SXSSF来进行压缩,当然性能也会有一定的影响。
SXSSFWorkbook wb = new SXSSFWorkbook(); wb.setCompressTempFiles(true);
临时文件:
SXSSF在导出的过程中会生成两种临时文件,一种是为每个sheet页生成一个 xml 临时文件,一种是最终导出时生成的完整.xlsx 文件
临时文件所在位置:
windows: C盘下的 AppData\Local\Temp\poifiles 例如我的:C:\Users\011336\AppData\Local\Temp\poifiles
Linux:在Tomcat目录下的 temp/poifiles
临时文件的删除:
workbook.write(fileOut); write()方法中包含删除 .xlsx 文件的方法,在它的finally代码块里,具体可以去查看源码
workbook.dispose(); dispose()方法就是用来删除那些 xml 格式的临时文件的
需要注意的细节:
每创建完一个sheet页就会生成一个xml文件 但是所有的 xml 文件都是空的,只有调用workbook.write(fileOut); 方法时,才会往xml中写数据,也就是说之前构造的几百万数据都在内存中,这是很危险的行为,当达到一定量时可能就会有内存溢出的风险,所以要记得在每个sheet页构造完成之后都手动把数据刷到磁盘当中((SXSSFSheet)sheet).flushRows();其实write()方法中也是for循环调用的flushRows()方法。
最关键的点:
记得点赞哦...
1. CommentController
/**
* excel导出功能
* @param commentSearch
* @param response
* @param request
* @return
* @throws Exception
*/
@RequestMapping("/exportCommentInfo")
@ResponseBody
@NoRepeatRequest
public BaseDTO exportCommentInfo(CommentSearch commentSearch, HttpServletResponse response, HttpServletRequest request) throws Exception{
LOGGER.info("CommentController.exportCommentInfo start");
long startTime = System.currentTimeMillis();
LOGGER.info("开始下载.........................................");
List<ErrorInfo> errors = null;
int result = 0;
String fileName = FileNameUtils.getExportCommontExcelFileName();
OutputStream fileOut = null;
SXSSFWorkbook workbook = null;try {
LOGGER.debug("classpath: " + fileName);
workbook = new SXSSFWorkbook(10000);//内存中实时存在10000个对象,超过的实时写入磁盘,保证内存消耗不会过大
commentService.exportCommentInfo(request,workbook, commentSearch);
// 定义excel文件名
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=\""
+ URLEncoder.encode(fileName, "UTF-8") + "\"");
// 定义输出流
fileOut = response.getOutputStream();
// 调用导出方法生成最终的 poi-sxssf-template.xlsx 临时文件,并且此方法包含删除此临时文件的方法
workbook.write(fileOut);
// 此方法能够删除导出过程中生成的xml临时文件
workbook.dispose();
} catch (Exception e) {
LOGGER.error("InterfaceInfoController.exportInterfaceInfo Exception: ", e);
ErrorInfo errorInfo = new ErrorInfo("system.error", "系统异常!");
errors = Arrays.asList(errorInfo);
}finally {
workbook.close();
}
LOGGER.info("下载完成....|||||.......用时:" + (System.currentTimeMillis() - startTime));
return tranferBaseDTO(errors, result);
}
2.导出实现类 exportCommentInfo
/**
* excel 导出
* @param: [request, workbook, commentSearch]
* @return: void
* @auther: 011336
* @date: 2018/12/7 15:03
*/
@Override
public void exportCommentInfo(HttpServletRequest request, SXSSFWorkbook workbook, CommentSearch commentSearch) {
//excel样式
CellStyle centerStyle = workbook.createCellStyle();
CellStyle cellStyleCenter = workbook.createCellStyle();
CellStyle cellStyleLeft = workbook.createCellStyle(); cellStyleCenter.setAlignment(HSSFCellStyle.ALIGN_CENTER); //水平布局:居中
cellStyleCenter.setVerticalAlignment(CellStyle.VERTICAL_CENTER);//垂直居中
cellStyleCenter.setWrapText(true);
cellStyleLeft.setAlignment(HSSFCellStyle.ALIGN_LEFT); //水平布局:居左
cellStyleLeft.setVerticalAlignment(CellStyle.VERTICAL_CENTER);//垂直居中
cellStyleLeft.setWrapText(true); Font font =workbook.createFont();
font.setColor(Font.COLOR_NORMAL); //字体颜色
font.setFontName("黑体"); //字体
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); //宽度 // 设置标题单元格类型
centerStyle.setFont(font);
centerStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER); //水平布局:居中
centerStyle.setWrapText(true);
centerStyle.setAlignment(XSSFCellStyle.ALIGN_CENTER);
centerStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);//设置前景填充样式
centerStyle.setFillForegroundColor(HSSFColor.GREY_50_PERCENT.index);//前景填充色
CreationHelper createHelper = workbook.getCreationHelper();
// 设置标题边框
centerStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);
centerStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);
centerStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);
centerStyle.setBorderTop(HSSFCellStyle.BORDER_THIN); //分批导出
int totalCount = countCommentNum(commentSearch);
//如果导出数据量大于 设定的最大数据量 则最多不能超过设定的数量
if(pageNumExport != null && totalCount > pageNumExport*limitExport){
totalCount = pageNumExport*limitExport;
}
int number = (totalCount % limitExport) == 0 ? totalCount
/ limitExport : totalCount / limitExport + 1;
List<CommentVo> commentVOs = new ArrayList<>();
for (int i = 0; i < number; i++) {long startTime = System.currentTimeMillis();
LOGGER.info("【第"+i+"】次开始查询数据库.........................................");
commentVOs = getCommentVoExport(commentSearch, i*limitExport, limitExport);
LOGGER.info("【第"+i+"】次数据库查询完成....|||||.......用时:" + (System.currentTimeMillis() - startTime));
int page = i+1;
Sheet sheet = workbook.createSheet("评论清单"+( page<10 ? "0"+page : page ) );
batchExport(request,sheet, commentSearch, commentVOs, centerStyle, cellStyleCenter, cellStyleLeft);
try {
((SXSSFSheet)sheet).flushRows();//每创建完成一个sheet页就把数据刷新到磁盘
} catch (IOException e) {
LOGGER.error("CommentServiceImpl.exportCommentInfo flushRows() exception ,that is not important"+e);
e.printStackTrace();
}
commentVOs.clear(); } }
-----提示:如果这里不手动刷的话,当所有sheet页都刷到内存中后,workbook.write()方法会采用for循环把所有的数据都刷到磁盘中,也就是说,如果这里不手动刷,那么所有的对象就都在内存中。
实测表明 即使它已经创建的xml临时文件,此时所有的xml临时文件也是空的,都只能通过最后的workbook.write()方法统一刷新到磁盘。那么就可能会有内存溢出的风险
3. batchExport(request,sheet, commentSearch, commentVOs, centerStyle, cellStyleCenter, cellStyleLeft); 的实现
/**
* 构造excel,赋值,样式
* @param: [request, sheet, commentSearch, commentVOs, centerStyle, cellStyleCenter, cellStyleLeft]
* @return: void
* @auther: 011336
* @date: 2018/12/7 15:20
*/
public void batchExport(HttpServletRequest request,Sheet sheet , CommentSearch commentSearch,List<CommentVo> commentVOs,
CellStyle centerStyle,CellStyle cellStyleCenter,CellStyle cellStyleLeft){
if (CollectionUtils.isEmpty(commentVOs)) {
LOGGER.debug("exportCommentInfo finish: " + commentVOs);
} Row newRowOfInparamTitle = sheet.createRow(0); String[] headerOfInParam = { "序号", "评价日期", "来源", "星级", "评论内容", "类型一级", "类型二级", "类型三级", "情感识别","建议类","订单号", "航班号", "航班日期","订单联系人","联系电话","备注"};
for (int j = 0; j < headerOfInParam.length; j++) {
newRowOfInparamTitle.createCell(j);
}
for (int j = 0; j < headerOfInParam.length; j++) {
sheet.getRow(0).getCell(j).setCellValue(new XSSFRichTextString(headerOfInParam[j]));
sheet.getRow(0).getCell(j).setCellStyle(centerStyle);
}
long startTime = System.currentTimeMillis();
LOGGER.info("构造表格开始.........................................");
for (int i = 0; i < commentVOs.size(); i++) {
CommentVo commentVo = commentVOs.get(i);
int index = i + 1;
Row createRow = sheet.createRow(i + 1);
for (int j = 0; j < headerOfInParam.length; j++) {
createRow.createCell(j);
}
sheet.getRow(i + 1).getCell(0).setCellValue(index + "");
sheet.getRow(i + 1).getCell(0).setCellStyle(cellStyleCenter);
sheet.getRow(i + 1).getCell(1).setCellValue(dealTrim(dateToStr2(commentVo.getCmtTime())));
sheet.getRow(i + 1).getCell(1).setCellStyle(cellStyleCenter);
sheet.getRow(i + 1).getCell(2).setCellValue(dealTrim(commentVo.getTerminalId()));
sheet.getRow(i + 1).getCell(2).setCellStyle(cellStyleCenter);
sheet.getRow(i + 1).getCell(3).setCellValue(dealTrim(commentVo.getCmtLevel()));
sheet.getRow(i + 1).getCell(3).setCellStyle(cellStyleCenter);
sheet.getRow(i + 1).getCell(4).setCellValue(dealTrim(commentVo.getCmtText()));
sheet.getRow(i + 1).getCell(4).setCellStyle(cellStyleLeft);
sheet.getRow(i + 1).getCell(5).setCellValue(dealTrim(dealTrim(commentVo.getClassfyFirst())));
sheet.getRow(i + 1).getCell(5).setCellStyle(cellStyleLeft);
sheet.getRow(i + 1).getCell(6).setCellValue(dealTrim(commentVo.getClassfySecond()));
sheet.getRow(i + 1).getCell(6).setCellStyle(cellStyleLeft);
sheet.getRow(i + 1).getCell(7).setCellValue(dealTrim(commentVo.getClassfyThird()));
sheet.getRow(i + 1).getCell(7).setCellStyle(cellStyleLeft);
String emotion = commentVo.getEmotion();
if("0".equals(emotion)){
emotion="差评";
}else if("1".equals(emotion)){
emotion="好评";
}
sheet.getRow(i + 1).getCell(8).setCellValue(dealTrim(emotion));
sheet.getRow(i + 1).getCell(8).setCellStyle(cellStyleCenter);
String isSuggestion = commentVo.getIsSuggestion();
if("0".equals(isSuggestion)){
isSuggestion="否";
}else if("1".equals(isSuggestion)){
isSuggestion="是";
}
sheet.getRow(i + 1).getCell(9).setCellValue(dealTrim(isSuggestion));
sheet.getRow(i + 1).getCell(9).setCellStyle(cellStyleCenter);
sheet.getRow(i + 1).getCell(10).setCellValue(dealTrim(commentVo.getOrderNo()));
sheet.getRow(i + 1).getCell(10).setCellStyle(cellStyleCenter);
sheet.getRow(i + 1).getCell(11).setCellValue(dealTrim(commentVo.getFlightNo()));
sheet.getRow(i + 1).getCell(11).setCellStyle(cellStyleCenter);
sheet.getRow(i + 1).getCell(12).setCellValue(dateToStr(commentVo.getFlightDate()));
sheet.getRow(i + 1).getCell(12).setCellStyle(cellStyleCenter);
sheet.getRow(i + 1).getCell(13).setCellValue(dealTrim(commentVo.getcName()));
sheet.getRow(i + 1).getCell(13).setCellStyle(cellStyleLeft);
sheet.getRow(i + 1).getCell(14).setCellValue(dealTrim(commentVo.getcTel()));
sheet.getRow(i + 1).getCell(14).setCellStyle(cellStyleLeft);
sheet.getRow(i + 1).getCell(15).setCellValue(dealTrim(commentVo.getRemark()));
sheet.getRow(i + 1).getCell(15).setCellStyle(cellStyleLeft);
if(i%1000 == 0){
request.getSession().setAttribute("currentNum",i+1);
}
}
LOGGER.info("构造表格结束....|||||.......用时:" + (System.currentTimeMillis() - startTime)); long startTime2 = System.currentTimeMillis();
LOGGER.info("处理行高开始.........................................");
dealColumWidth( headerOfInParam, sheet,commentVOs, request);
LOGGER.info("处理行高结束....|||||.......用时:" + (System.currentTimeMillis() - startTime2));
}
4. 处理行高代码的实现 dealColumWidth( headerOfInParam, sheet,commentVOs, request);这里主要是设置固定列宽,然后自适应行高,二期自适应行高他是不提供方法的,只能自己去计算
/**
* 设置列宽
* @param: [headerOfInParam, sheet, commentVOs, request]
* @return: void
* @auther: 011336
* @date: 2018/12/7 15:20
*/
public void dealColumWidth(String[] headerOfInParam,Sheet sheet,List<CommentVo> commentVOs,HttpServletRequest request){
//单独处理 评论内容 和 评论备注 以及 分类 的宽度 和高度
sheet.setColumnWidth(1, 5500);//11个汉字 评价日期
sheet.setColumnWidth(2, 1500);//3个汉字 来源
sheet.setColumnWidth(3, 1500);//3个汉字 星级
sheet.setColumnWidth(4, 12000);//24个汉字 评论
sheet.setColumnWidth(5, 3000);//6个汉字 分类1
sheet.setColumnWidth(6, 3000);//6个汉字 分类2
sheet.setColumnWidth(7, 4000);//8个汉字 分类3
sheet.setColumnWidth(8, 2500);//5个汉字 情感识别
sheet.setColumnWidth(9, 2000);//4个汉字 建议类
sheet.setColumnWidth(10, 2500);//5个汉字 订单号
sheet.setColumnWidth(11, 2500);//5个汉字 航班号
sheet.setColumnWidth(12, 3500);//7个汉字 航班日期
sheet.setColumnWidth(13, 4000);//8个汉字 订单联系人
sheet.setColumnWidth(14, 4000);//8个汉字 联系电话
sheet.setColumnWidth(15, 10000);//20个汉字 备注 double rn = 20.00 , cn = 24.00 ,c1 = 6.00 ,c2 = 6.00 ,c3 = 8.00 , nn = 8.00;//ca应该为20可是导出结果却换行不对所以改成16保险一点
//计算 设置自定义行高
for (int i = 0; i < commentVOs.size(); i++) {
//计算行高
CommentVo commentVo = commentVOs.get(i);
int charNumC1 = (int)Math.ceil( dealTrim(commentVo.getClassfyFirst()).length() / c1 );//分类1
int charNumC2 = (int)Math.ceil( dealTrim(commentVo.getClassfySecond()).length() / c2 );//分类2
int charNumC3 = (int)Math.ceil( dealTrim(commentVo.getClassfyThird()).length() / c3 );//分类2
int charNumRemark = (int)Math.ceil( dealTrim(commentVo.getRemark()).length() / rn );//备注
int charNumComent = (int)Math.ceil( dealTrim(commentVo.getCmtText()).length() / cn);//评论
int charNumName = (int)Math.ceil( dealTrim(commentVo.getcName()).length() / nn);//订单联系人
List<Integer> input = new ArrayList<Integer>();
//input.add(charNum+num);//charNum 是分类的条数,每条一行。num是分类字数大于20的情况就多分配一行
input.add(charNumC1);
input.add(charNumC2);
input.add(charNumC3);
input.add(charNumRemark);
input.add(charNumComent);
input.add(charNumName);
int rowNum = Collections.max(input); Row row = sheet.getRow(i+1);
row.setHeight((short)(sheet.getDefaultRowHeight()*rowNum));
}
request.getSession().setAttribute("currentNum",commentVOs.size());
}
代码中的request可忽略,那是我做的进度条,
poi实现百万级数据导出的更多相关文章
- PHP百万级数据导出方案(多csv文件压缩)
本文转自网络仅供学习之用 概述: 最近公司项目要求把数据除了页面输出也希望有导出功能,虽然之前也做过几个导出功能,但这次数据量相对比较大,差不多一天数据就20W条,要求导7天或者30天,那么数据量就轻 ...
- PHP 百万级数据导出方案(多 CSV 文件压缩)
ps:来源 :https://laravel-china.org/articles/15944/php-million-level-data-export-scheme-multi-csv-file- ...
- PHP如何实现百万级数据导出
公司目前有一个需求,需要对一个日增量在20万+数据量的数据表中的数据进行可自定义条件筛选的导出数据,该功能需要对多个部门进行开发使用,要保证功能可用的前提下,尽量优化体验. 首先介绍一下当前可利用的资 ...
- Mysql百万级数据索引重新排序
参考https://blog.csdn.net/pengshuai007/article/details/86021689中思路解决自增id重排 方式一 alter table `table_name ...
- 实战手记:让百万级数据瞬间导入SQL Server
想必每个DBA都喜欢挑战数据导入时间,用时越短工作效率越高,也充分的能够证明自己的实力.实际工作中有时候需要把大量数据导入数据库,然后用于各种程序计算,本文将向大家推荐一个挑战4秒极限让百万级数据瞬间 ...
- 【转 】实战手记:让百万级数据瞬间导入SQL Server
想必每个DBA都喜欢挑战数据导入时间,用时越短工作效率越高,也充分的能够证明自己的实力.实际工作中有时候需要把大量数据导入数据库,然后用于各种程序计算,本文将向大家推荐一个挑战4秒极限让百万级数据瞬间 ...
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试
原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试 系列目录 我想测试EF在一百万条数据下的显示时间! ...
- poi合并单元格同时导出excel
poi合并单元格同时导出excel POI进行跨行需要用到对象HSSFSheet对象,现在就当我们程序已经定义了一个HSSFSheet对象sheet. 跨第1行第1个到第2个单元格的操作为 sheet ...
- EF查询百万级数据的性能测试--多表连接复杂查询
相关文章:EF查询百万级数据的性能测试--单表查询 一.起因 上次做的是EF百万级数据的单表查询,总结了一下,在200w以下的数据量的情况(Sql Server 2012),EF是可以使用,但是由于 ...
随机推荐
- python的类和实例化对象
一切皆对象,类也是对象,类来自于元类type,如果一个类没有声明自己的元类,默认它就是元类. 即类是元类的实例,通过type(类)会显示type,而实例来自于类. 类有两个属性,数据属性和函数属性,下 ...
- JavaScript实现表单验证_02
注册3次错误,最终的结果: 代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8" ...
- AMQP 0.9.1和1.0协议差别以及rabbitmq支持情况
RabbitMQ implements AMQP 1.0 via a plugin. However, AMQP 1.0 is a completely different protocol than ...
- python简说(三)字典
一.字典 stu_info = {"name": "王志华", "age": 18, "addr": "北京& ...
- springmvc配置文件<context:component-scan>
在spring.xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的 ...
- javascript对文件的读写
整合了一下网上对于js实现文件读写的代码,但是该功能只能在ie浏览器下执行,另外有些电脑上的ie需要设置. 下面是写入代码: var fso = new ActiveXObject("Scr ...
- P4556 [Vani有约会]雨天的尾巴
目录 思路 优化 过程中的问题/疑问 错误 代码 思路 每个节点维护一课线段树(当然是动态开点) 线段树的作用是统计这个节点有多少种粮食型号,以及最多的粮食型号 然后树上差分,u和v点 +1,lca( ...
- 转载:Systemd 命令
目录 一.由来 二.Systemd 概述 三.系统管理 3.1 systemctl 3.2 systemd-analyze 3.3 hostnamectl 3.4 localectl 3.5 time ...
- CentOS7.2 安装Docker
Docker 要求 CentOS 系统的内核版本高于 3.10 ,查看本页面的前提条件来验证你的CentOS 版本是否支持 Docker . 通过 uname -r 命令查看你当前的内核版本 [roo ...
- PTA 7-1 整数分解为若干项之和(20 分)
7-1 整数分解为若干项之和(20 分) 将一个正整数N分解成几个正整数相加,可以有多种分解方法,例如7=6+1,7=5+2,7=5+1+1,….编程求出正整数N的所有整数分解式子. 输入格式: 每个 ...