什么?你还在用POI导出数据?EasyExcel解决大数据量导出OOM(内存溢出)

前言

今天忙完,上面派发了一个任务,有个项目的导出接口数据量太大了,导出直接内存溢出(OOM),暂时做法是限制导出的行数,然后让我去研究一下,通过一下午的研究,通过EasyExcel解决了这个问题,并且大幅度提高了映射速度,如下图:
在这里插入图片描述

EasyExcel介绍

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目,alibaba旗下的高性能处理Excel工具。在尽可能节约内存的情况下支持读写百M的Excel.

Java解析、生成Excel比较常用的框架有Apache POI、JXL,但是他们都有一个严重的问题就是耗内存,POI有一套SAX模式的API可以一定程度上解决一些内存溢出的问题,但POI还是有一些缺陷

比如:07版本的Excel解压后存储都是在内存中完成的,内存消耗依旧很大。EasyExcel重写了POI对07版本Excel的解析,能够让原本一个3M的Excel用POI SAX需要用到100M左右内存降低到几M,并且再大的Excel都不会出现内存溢出,并且它在上层做了模型转换的封装,让使用者更简单。

在这里插入图片描述
64M内存1分钟内读取75M(46W行25列)的Excel,以上来源于官网↑↑

Github 开源地址:GitHub地址
语雀案例地址:官网案例

快速使用

导入Maven依赖 ps:这里只做导出数据示范,更多操作请参考官网

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>2.2.4</version>
</dependency>

实体类(模版对象):

@Data
public class DemoData {
	@ExcelProperty("字符串标题")
	private String string;
	// 这里用string 去接日期才能格式化。我想接收年月日格式
	@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
	@ExcelProperty("日期标题")
	private Date date;
	@ExcelProperty("数字标题")
	//我想接收百分比的数字
	// @NumberFormat("#.##%")
	private Double doubleData;
	//忽略这个字段
	@ExcelIgnore
	private String ignore;
}

测试:

@Test
public void yy(){
  // 写法1
  String fileName = "D:\\Program Files (x86)\\program\\excel\\easyExcel\\" + "simpleWrite.xlsx";
  // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
  EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
private List<DemoData> data() {
  List<DemoData> list = new ArrayList<DemoData>();
  for (int i = 0; i < 10; i++) {
   DemoData data = new DemoData();
   data.setString("字符串" + i);
   data.setDate(new Date());
   data.setDoubleData(0.56);
   list.add(data);
}
   return list;
}

效果展示:
在这里插入图片描述

项目实战使用

简单了解完EasyExcel的用法,就可以把它整合到我们的项目中了,先简单说说我项目中导出的数据为什么会内存溢出(OOM)

其实导出的数据行也就4000多行,但是每行的列多达78列,别问我为什么会这么多,所以行数到达3000上以上, 大概率会出现内存溢出
在这里插入图片描述
我们公司框架封装了POI的一套用法,我现在要做的就是把用POI导出的用法替换为EasyExcel

1.创建导出POJO模板

@Data
@ContentRowHeight(10)
@HeadRowHeight(15) // 标题长度
@ColumnWidth(15) // 标题宽度
public class EmergencyeventData {
    @ExcelProperty("序号")
    private Integer id;

    @ExcelProperty("项目名称")
    private String projectName;

    @DateTimeFormat("yyyy年MM月dd日 HH:mm:ss")
    @ExcelProperty("录入时间")
    private String createDate;

    @DateTimeFormat("yyyy年MM月dd日 HH:mm:ss")
    @ExcelProperty("日期")
    private String detectionTime;
  }

↑以上省略了74个属性(ps:写完真的累)

原来项目封装的POI用法:↓

     List<ExcelHeader> headers = new ArrayList<>();
    headers.add(new ExcelHeader("序号", true));
    headers.add(new ExcelHeader("项目名称", "projectName", String.class));
    headers.add(new ExcelHeader("录入时间", 1, "createDate", Date.class, "yyyy年MM月dd日 HH:mm:ss"));
    headers.add(new ExcelHeader("日期", "detectionTime", String.class));

原来项目封装POI的用法是通过一个List<Map<String, Object>>进行封装映射数据,通过Map中对应的key来符合↑↑代码中的key,这不重要,现在我只需要把List<Map<String, Object>>中的数据塞到我定义的模板对象集合中即可,如下伪代码所示:

private  List<EmergencyeventData> EasyExcelData(List<Map<String, String>>){ 
if(SysFun.isNotEmpty(data.get("endTimes"))){
    eventData.setDocName(data.get("endTimes").toString());
}
 emergencyeventDataList.add(eventData);
 }

2.响应数据,返回文件流

项目是前后端分离,所以接口返回给前端的应该是文件流的形式,这里给出我的示例:

//设置响应格式
    try {
        ExcelResponse.responseHeader(response, XXX.getName());
    } catch (UnsupportedEncodingException e) {
        return ResultForm.createErrorResultForm(null, e.getMessage());
    }

给出我的响应工具类中的方法代码:

//封装响应流
public static void responseHeader(HttpServletResponse response, String name) throws UnsupportedEncodingException {
    Date d = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    String date = sdf.format(d);
    String fileName = name;
    String encodeName = java.net.URLEncoder.encode(fileName, "UTF-8");
    response.setContentType("application/octet-stream");
    response.setHeader("Access-control-Expose-Headers", "attachment");
    response.setHeader("attachment", encodeName + ".xlsx");
}

写入数据,返回文件流:

// 写入的参数为输出流,模板对象,封装好的集合数据,写完数据,文件流会自动关闭
try {
        EasyExcel.write(response.getOutputStream(), EmergencyeventData.class).sheet("事件").doWrite(emergencyeventData);
    } catch (Exception e) {
        return ResultForm.createErrorResultForm(null, e.getMessage());
    }

以上可以直接使用Swagger-ui直接进行接口测试

结尾

没用过EasyExcel 的一定要去用用,比POI更容易上手,性能更好,关于文中的不足欢迎指出!!写博客不是为了写博客而去写博客,分享一点自己遇到的问题,能够帮助大家的博客才有意义。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Java项目中使用EasyExcel导出数据时,如果数据超过了Excel 2003(即xls格式)的最大行数65536,可以采用以下两种方式来解决: 1. 使用Excel 2007或以上版本的xlsx格式,该格式最大支持1048576行,可以满足大部分需求。只需要在EasyExcel的写操作中将文件格式设置为xlsx即可: ```java // 导出Excel,文件格式为xlsx EasyExcel.write(fileName, dataClass).excelType(ExcelTypeEnum.XLSX).sheet(sheetName).doWrite(dataList); ``` 2. 将数据分批次导出,每次导出数据不超过65536行,最后再将多个Excel文件合并成一个。可以使用Apache POI中的Workbook类来实现Excel文件的合并,具体实现方法可以参考以下代码: ```java // 初始化Workbook对象 Workbook wb = new HSSFWorkbook(); // 遍历多个Excel文件,将每个Excel文件的数据读入到Workbook对象中 for (int i = 0; i < fileCount; i++) { InputStream inputStream = new FileInputStream("文件路径"); Workbook tmpWb = WorkbookFactory.create(inputStream); int sheetCount = tmpWb.getNumberOfSheets(); for (int j = 0; j < sheetCount; j++) { Sheet tmpSheet = tmpWb.getSheetAt(j); Sheet sheet = wb.createSheet(tmpSheet.getSheetName()); int rowCount = tmpSheet.getLastRowNum(); for (int k = 0; k <= rowCount; k++) { Row tmpRow = tmpSheet.getRow(k); Row row = sheet.createRow(k); int cellCount = tmpRow.getLastCellNum(); for (int l = 0; l < cellCount; l++) { Cell tmpCell = tmpRow.getCell(l); Cell cell = row.createCell(l); if (tmpCell != null) { cell.setCellStyle(tmpCell.getCellStyle()); int cellType = tmpCell.getCellType(); switch (cellType) { case Cell.CELL_TYPE_BLANK: break; case Cell.CELL_TYPE_BOOLEAN: cell.setCellValue(tmpCell.getBooleanCellValue()); break; case Cell.CELL_TYPE_ERROR: cell.setCellValue(tmpCell.getErrorCellValue()); break; case Cell.CELL_TYPE_FORMULA: cell.setCellFormula(tmpCell.getCellFormula()); break; case Cell.CELL_TYPE_NUMERIC: cell.setCellValue(tmpCell.getNumericCellValue()); break; case Cell.CELL_TYPE_STRING: cell.setCellValue(tmpCell.getStringCellValue()); break; } } } } } } // 将合并后的数据写入到一个Excel文件中 FileOutputStream fileOut = new FileOutputStream("输出文件路径"); wb.write(fileOut); fileOut.close(); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值