Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便。官方网站
16M内存23秒读取75M(46W行25列)的Excel(3.2.1+版本)
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>3.2.1version>
dependency>
模板文件里面的变量对应实体类的属性,{.aaa}这种类型的是集合,{aaa}这种类型的是对象。
package com.test.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.test.domain.Project;
import com.test.utils.ExcelFillCellMergeStrategy;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.*;
/**
*
*
* @author linfeng
* @date 2023-03-25 17:49
*/
@Controller
@RequestMapping("/excel/test")
public class ExcelTestController {
@GetMapping("/export")
public void export(HttpServletResponse response) throws Exception{
String templateFilePath = "C:\\Users\\DELL\\Desktop\\2023年度重点洽谈项目汇总表.xlsx";
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("2023年度重点洽谈项目汇总表"+System.currentTimeMillis()/1000+".xlsx", "utf-8");
response.setHeader("Content-disposition", "attachment; filename=" + new String(fileName.getBytes("UTF-8"), "ISO-8859-1"));
//设置第几列开始合并
int[] mergeColumnIndex = {0};
//设置第几行开始合并
int mergeRowIndex = 2;
OutputStream outputStream = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
ExcelWriter excelWriter = EasyExcel.write(bos).withTemplate(templateFilePath)
.registerWriteHandler(new ExcelFillCellMergeStrategy(mergeRowIndex,mergeColumnIndex))
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
Map<String,Object> excelDataMap = new HashMap<>(16);
List<Project> projectList = getProjectList();
excelDataMap.put("year",2023);
excelWriter.fill(excelDataMap,writeSheet);
excelWriter.fill(projectList,writeSheet);
excelWriter.finish();
bos.flush();
}
public static List<Project> getProjectList(){
List<Project> projectList = new ArrayList<>();
projectList.add(new Project("成华区","计划洽谈",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("成华区","进行中",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("成华区","完成",20L,29L,2L,5L,30L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("武侯区","计划洽谈",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("武侯区","进行中",20L,10L,2L,5L,78L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("武侯区","完成",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("高新区","计划洽谈",60L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("高新区","进行中",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("高新区","完成",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("锦江区","计划洽谈",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("锦江区","进行中",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("锦江区","完成",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("金牛区","计划洽谈",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("金牛区","进行中",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("金牛区","完成",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("龙泉区","计划洽谈",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("龙泉区","进行中",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("龙泉区","完成",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("青羊区","计划洽谈",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("青羊区","进行中",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("青羊区","完成",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("温江区","计划洽谈",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("温江区","进行中",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
projectList.add(new Project("温江区","完成",20L,10L,2L,5L,100L,200L,10L,98L,34L,43L,45L,90L));
return projectList;
}
}
public class Project {
/** 项目名称 */
private String projectName;
/** 项目类型 */
private String projectType;
/** 一月数据量 */
private Long januaryNumber;
/** 二月数据量 */
private Long februaryNumber;
/** 三月数据量 */
private Long marchNumber;
/** 四月数据量 */
private Long aprilNumber;
/** 五月数据量 */
private Long mayNumber;
/** 六月数据量 */
private Long juneNumber;
/** 七月数据量 */
private Long julyNumber;
/** 八月数据量 */
private Long augustNumber;
/** 九月数据量 */
private Long septemberNumber;
/** 十月数据量 */
private Long octoberNumber;
/** 十一月数据量 */
private Long novemberNumber;
/** 十二月数据量 */
private Long decemberNumber;
public Project() {}
public Project(String projectName, String projectType, Long januaryNumber, Long februaryNumber, Long marchNumber, Long aprilNumber, Long mayNumber, Long juneNumber, Long julyNumber, Long augustNumber, Long septemberNumber, Long octoberNumber, Long novemberNumber, Long decemberNumber) {
this.projectName = projectName;
this.projectType = projectType;
this.januaryNumber = januaryNumber;
this.februaryNumber = februaryNumber;
this.marchNumber = marchNumber;
this.aprilNumber = aprilNumber;
this.mayNumber = mayNumber;
this.juneNumber = juneNumber;
this.julyNumber = julyNumber;
this.augustNumber = augustNumber;
this.septemberNumber = septemberNumber;
this.octoberNumber = octoberNumber;
this.novemberNumber = novemberNumber;
this.decemberNumber = decemberNumber;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getProjectType() {
return projectType;
}
public void setProjectType(String projectType) {
this.projectType = projectType;
}
public Long getJanuaryNumber() {
return januaryNumber;
}
public void setJanuaryNumber(Long januaryNumber) {
this.januaryNumber = januaryNumber;
}
public Long getFebruaryNumber() {
return februaryNumber;
}
public void setFebruaryNumber(Long februaryNumber) {
this.februaryNumber = februaryNumber;
}
public Long getMarchNumber() {
return marchNumber;
}
public void setMarchNumber(Long marchNumber) {
this.marchNumber = marchNumber;
}
public Long getAprilNumber() {
return aprilNumber;
}
public void setAprilNumber(Long aprilNumber) {
this.aprilNumber = aprilNumber;
}
public Long getMayNumber() {
return mayNumber;
}
public void setMayNumber(Long mayNumber) {
this.mayNumber = mayNumber;
}
public Long getJuneNumber() {
return juneNumber;
}
public void setJuneNumber(Long juneNumber) {
this.juneNumber = juneNumber;
}
public Long getJulyNumber() {
return julyNumber;
}
public void setJulyNumber(Long julyNumber) {
this.julyNumber = julyNumber;
}
public Long getAugustNumber() {
return augustNumber;
}
public void setAugustNumber(Long augustNumber) {
this.augustNumber = augustNumber;
}
public Long getSeptemberNumber() {
return septemberNumber;
}
public void setSeptemberNumber(Long septemberNumber) {
this.septemberNumber = septemberNumber;
}
public Long getOctoberNumber() {
return octoberNumber;
}
public void setOctoberNumber(Long octoberNumber) {
this.octoberNumber = octoberNumber;
}
public Long getNovemberNumber() {
return novemberNumber;
}
public void setNovemberNumber(Long novemberNumber) {
this.novemberNumber = novemberNumber;
}
public Long getDecemberNumber() {
return decemberNumber;
}
public void setDecemberNumber(Long decemberNumber) {
this.decemberNumber = decemberNumber;
}
}
合并单元格前提条件,数据一样。上面我写的测试数据,其中有三个单元格数据是一样的。
package com.test.utils;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.List;
/**
* @author linfeng
* @date 2023-03-25 21:06
*/
public class ExcelFillCellMergeStrategy implements CellWriteHandler {
/**
* 合并字段的下标,如第一到五列new int[]{0,1,2,3,4}
*/
private int[] mergeColumnIndex;
/**
* 从第几行开始合并,如果表头占两行,这个数字就是2
*/
private int mergeRowIndex;
public ExcelFillCellMergeStrategy() {
}
public ExcelFillCellMergeStrategy(int mergeRowIndex, int[] mergeColumnIndex) {
this.mergeRowIndex = mergeRowIndex;
this.mergeColumnIndex = mergeColumnIndex;
}
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
Head head, Integer integer, Integer integer1, Boolean aBoolean) {
}
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
Head head, Integer integer, Boolean aBoolean) {
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
List<WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
//当前行
int curRowIndex = cell.getRowIndex();
//当前列
int curColIndex = cell.getColumnIndex();
if (curRowIndex > mergeRowIndex) {
for (int i = 0; i < mergeColumnIndex.length; i++) {
if (curColIndex == mergeColumnIndex[i]) {
mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
break;
}
}
}
}
private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
//获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并
Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() :
cell.getNumericCellValue();
Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() :
preCell.getNumericCellValue();
// 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行
if (curData.equals(preData)) {
Sheet sheet = writeSheetHolder.getSheet();
List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
boolean isMerged = false;
for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
CellRangeAddress cellRangeAddr = mergeRegions.get(i);
// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
sheet.removeMergedRegion(i);
cellRangeAddr.setLastRow(curRowIndex);
sheet.addMergedRegion(cellRangeAddr);
isMerged = true;
}
}
// 若上一个单元格未被合并,则新增合并单元
if (!isMerged) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex,
curColIndex);
sheet.addMergedRegion(cellRangeAddress);
}
}
}
public int[] getMergeColumnIndex() {
return mergeColumnIndex;
}
public void setMergeColumnIndex(int[] mergeColumnIndex) {
this.mergeColumnIndex = mergeColumnIndex;
}
public int getMergeRowIndex() {
return mergeRowIndex;
}
public void setMergeRowIndex(int mergeRowIndex) {
this.mergeRowIndex = mergeRowIndex;
}
}