- 1. Java操作Excel概述
- 1.1 Excel需求概述
- 1.2 Excel操作三种方式对比
- 2. ApachePOIExcel
- 2.1 ApachePOI简介
- 2.2 ApachePOI功能结构
- 2.3 ApachePOI官网说明
- 2.4 ApachePOI实现验证
- 3. HutoolExcel
- 3.1 Hutool简介
- 3.2 Hutool组件
- 3.1 HutoolExcel实现验证
- 4. EasyExcel
- 4.1 EasyExcel简介
- 4.2 HutoolExcel实现验证
- 5. 源码地址
Excel作为常用的报表文件,项目开发中经常用到Excel操作功能,对于Excel操作目前常见的有三种方式,Apache的POI,Hutool的ExcelUtil,Ali的EasyExcel。
1.2 Excel操作三种方式对比Apache的POI相对比较原生,功能强大,使用复杂繁琐,内存占用比较大。
Hutool的ExcelUtil在POI基础上进行封装,简单易用,功能不太全,对于一些特殊操作,如标注等不具备。
Ali的EasyExcel也是在POI基础上进行封装,简单易用,功能相对比较全面,且性能十分强悍,最为推荐。
Apache POI [1] 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。
2.2 ApachePOI功能结构HSSF [1] - 提供读写Microsoft Excel XLS格式档案的功能。 XSSF [1] - 提供读写Microsoft Excel OOXML XLSX格式档案的功能。 HWPF [1] - 提供读写Microsoft Word DOC格式档案的功能。 HSLF [1] - 提供读写Microsoft PowerPoint格式档案的功能。 HDGF [1] - 提供读Microsoft Visio格式档案的功能。 HPBF [1] - 提供读Microsoft Publisher格式档案的功能。 HSMF [1] - 提供读Microsoft Outlook格式档案的功能。2.3 ApachePOI官网说明
POI官网地址:https://poi.apache.org/index.html
POI下载地址:https://poi.apache.org/download.html
POI文档说明:https://poi.apache.org/apidocs/index.html
2.4 ApachePOI实现验证Maven依赖配置
org.apache.poi
poi
4.0.1
org.apache.poi
poi-ooxml
4.0.1
org.apache.poi
poi-ooxml-schemas
4.0.1
代码测试
package com.zrj.easyexcel.excel;
import com.google.common.collect.Lists;
import org.junit.Test;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EasyExcelPoi {
@Test
public void createExcelTest() throws Exception {
//文件的基础路径
String fileBasePath = System.getProperty("user.dir") + File.separator + "excel" + File.separator;
System.out.println("文件的基础路径:" + fileBasePath);
String filepath = fileBasePath + "writeexcel.xlsx";
String sheetName = "模板名称";
List titles = Lists.newArrayList("name", "sex", "age");
List
3. HutoolExcel
3.1 Hutool简介
Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;
Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。
项目中使用非常广泛的一个小工具,没有依赖,非常干净好用。
官网地址:https://www.hutool.cn/
参考文档:https://www.hutool.cn/docs/#/
API文档:https://apidoc.gitee.com/dromara/hutool/
GITEE:https://gitee.com/dromara/hutool/
GITHUB:https://github.com/dromara/hutool/
视频介绍:https://player.bilibili.com/player.html?aid=710062843&bvid=BV1bQ4y1M7d9&cid=170377135&page=2
3.2 Hutool组件 3.1 HutoolExcel实现验证Mave依赖配置
cn.hutool hutool-all 5.7.18
代码实现验证
package com.zrj.easyexcel.excel;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.zrj.easyexcel.utils.FileUtils;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class EasyExcelHutool {
@Test
public void readExcelObjectTest() {
//定义文件路径
String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";
//通过工具类创建ExcelReader
ExcelReader reader = ExcelUtil.getReader(filepath);
List> readAll = reader.read();
System.out.println("Excel读取结果:" + readAll);
}
@Test
public void readExcelMapTest() {
//定义文件路径
String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";
//通过工具类创建ExcelReader
ExcelReader reader = ExcelUtil.getReader(filepath);
List
4. EasyExcel
4.1 EasyExcel简介
EasyExcel是阿里巴巴开源的一款Excel操作工具,简单易用,功能强大,节省内存,性能彪悍。
语雀地址:https://www.yuque.com/easyexcel/doc/easyexcel
Github地址: https://github.com/alibaba/easyexcel
mvn依赖地址:https://mvnrepository.com/artifact/com.alibaba/easyexcel
4.2 HutoolExcel实现验证Mave依赖配置
com.alibaba easyexcel 3.0.5
EasyExcel基本实现案例
Github案例地址:https://github.com/alibaba/easyexcel
项目路径:test -> read/write -> ReadTest与WriteTest类,实现基本Excel各种读写功能。
EasyExcel批量添加批注
ByteToInputStreamUtil
package com.zrj.easyexcel.excel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
public class ByteToInputStreamUtil {
public static InputStream byte2Input(byte[] buf) {
return new ByteArrayInputStream(buf);
}
public static byte[] input2byte(InputStream inStream) throws Exception {
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
byte[] buff = new byte[100];
int rc = 0;
while ((rc = inStream.read(buff, 0, 100)) > 0) {
swapStream.write(buff, 0, rc);
}
byte[] in2b = swapStream.toByteArray();
return in2b;
}
}
CommentWriteHandler
package com.zrj.easyexcel.excel;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.write.handler.AbstractRowWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CommentWriteHandler extends AbstractRowWriteHandler {
public static final String SHEETNAME_NAME = "sheetName";
private String extension;
public static final String COLINDEX_NAME = "colIndex";
public static final String ROWINDEX_NAME = "rowIndex";
public static final String COMMENTCONTENT_NAME = "commentContent";
private List sheetNameList;
List> commentList = new ArrayList<>(10);
public CommentWriteHandler(List> commentList, String extension) {
this.commentList = commentList != null && commentList.size() > 0
? commentList.stream().filter(x ->
x.keySet().contains(SHEETNAME_NAME) == true && x.get(SHEETNAME_NAME) != null && StrUtil.isNotBlank(x.get(SHEETNAME_NAME).toString())
&& x.keySet().contains(COLINDEX_NAME) == true && x.get(COLINDEX_NAME) != null && StrUtil.isNotBlank(x.get(COLINDEX_NAME).toString())
&& x.keySet().contains(ROWINDEX_NAME) == true && x.get(ROWINDEX_NAME) != null && StrUtil.isNotBlank(x.get(ROWINDEX_NAME).toString())
&& x.keySet().contains(COMMENTCONTENT_NAME) == true && x.get(COMMENTCONTENT_NAME) != null && StrUtil.isNotBlank(x.get(COMMENTCONTENT_NAME).toString())
).collect(Collectors.toList()) : new ArrayList<>();
sheetNameList = this.commentList.stream().map(x -> x.get(SHEETNAME_NAME).toString()).collect(Collectors.toList());
this.extension = extension;
}
public static Map createCommentMap(String sheetName, int rowIndex, int columnIndex, String commentContent) {
Map map = new HashMap<>();
//sheet页名称
map.put(SHEETNAME_NAME, sheetName);
//行号
map.put(ROWINDEX_NAME, rowIndex + "");
//列号
map.put(COLINDEX_NAME, columnIndex + "");
//批注内容
map.put(COMMENTCONTENT_NAME, commentContent);
return map;
}
@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
Integer relativeRowIndex, Boolean isHead) {
Sheet sheet = writeSheetHolder.getSheet();
//不需要添加批注,或者当前sheet页不需要添加批注
if (commentList == null || commentList.size() <= 0 || sheetNameList.contains(sheet.getSheetName()) == false) {
return;
}
//获取当前行的批注信息
List> rowCommentList = commentList.stream().filter(x ->
StrUtil.equals(x.get(SHEETNAME_NAME).toString(), sheet.getSheetName())
&& relativeRowIndex == Integer.parseInt(x.get(ROWINDEX_NAME))).collect(Collectors.toList());
//当前行没有批注信息
if (rowCommentList == null || rowCommentList.size() <= 0) {
return;
}
List colIndexList = rowCommentList.stream().map(x -> x.get(COLINDEX_NAME)).distinct().collect(Collectors.toList());
for (String colIndex : colIndexList) {
//同一单元格的批注信息
List> cellCommentList = rowCommentList.stream().filter(x ->
StrUtil.equals(colIndex, x.get(COLINDEX_NAME))).collect(Collectors.toList());
if (CollectionUtil.isEmpty(cellCommentList)) {
continue;
}
//批注内容拼成一条
String commentContent = cellCommentList.stream().map(x -> x.get(COMMENTCONTENT_NAME)).collect(Collectors.joining());
Cell cell = row.getCell(Integer.parseInt(colIndex));
addComment(cell, commentContent, extension);
}
//删除批注信息
commentList.remove(rowCommentList);
//重新获取要添加的sheet页姓名
sheetNameList = commentList.stream().map(x -> x.get(SHEETNAME_NAME).toString()).collect(Collectors.toList());
}
public static void addComment(Cell cell, String value, String extension) {
Sheet sheet = cell.getSheet();
cell.removeCellComment();
if ("xls".equals(extension)) {
ClientAnchor anchor = new HSSFClientAnchor();
// 关键修改
anchor.setDx1(0);
anchor.setDx2(0);
anchor.setDy1(0);
anchor.setDy2(0);
anchor.setCol1(cell.getColumnIndex());
anchor.setRow1(cell.getRowIndex());
anchor.setCol2(cell.getColumnIndex() + 5);
anchor.setRow2(cell.getRowIndex() + 6);
// 结束
Drawing drawing = sheet.createDrawingPatriarch();
Comment comment = drawing.createCellComment(anchor);
// 输入批注信息
comment.setString(new HSSFRichTextString(value));
// 将批注添加到单元格对象中
cell.setCellComment(comment);
} else if ("xlsx".equals(extension)) {
ClientAnchor anchor = new XSSFClientAnchor();
// 关键修改
anchor.setDx1(0);
anchor.setDx2(0);
anchor.setDy1(0);
anchor.setDy2(0);
anchor.setCol1(cell.getColumnIndex());
anchor.setRow1(cell.getRowIndex());
anchor.setCol2(cell.getColumnIndex() + 5);
anchor.setRow2(cell.getRowIndex() + 6);
// 结束
Drawing drawing = sheet.createDrawingPatriarch();
Comment comment = drawing.createCellComment(anchor);
// 输入批注信息
comment.setString(new XSSFRichTextString(value));
// 将批注添加到单元格对象中
cell.setCellComment(comment);
}
}
}
EasyExcelUtils
package com.zrj.easyexcel.excel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.zrj.easyexcel.entity.DemoData;
import com.zrj.easyexcel.excel.read.ReadTest;
import com.zrj.easyexcel.excel.write.WriteTest;
import org.junit.Test;
import java.io.File;
import java.util.*;
public class EasyExcelUtils {
@Test
public void easyExcelReadWriteTest() {
//EasyExcel读测试类
ReadTest readTest = new ReadTest();
//EasyExcel写测试类
WriteTest writeTest = new WriteTest();
}
@Test
public void batchAddCommentTest() {
try {
//构建数据
List demoDataList = new ArrayList<>(10);
demoDataList.add(DemoData.builder().string("张三").date(new Date()).doubleData(3.14).build());
demoDataList.add(DemoData.builder().string("王五").date(new Date()).doubleData(6.68).build());
demoDataList.add(DemoData.builder().string("赵六").date(new Date()).doubleData(8.32).build());
demoDataList.add(DemoData.builder().string("李四").date(new Date()).doubleData(8.66).build());
//定义文件路径
String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "EasyExcelCommentWriteTest3.xlsx";
String sheetName = "批注模板";
List> commentList = new ArrayList<>();
commentList.add(CommentWriteHandler.createCommentMap(sheetName, 0, 1, "第一条批注。"));
commentList.add(CommentWriteHandler.createCommentMap(sheetName, 1, 1, "第二条批注。"));
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 这里要注意inMemory 要设置为true,才能支持批注。目前没有好的办法解决 不在内存处理批注。这个需要自己选择。
EasyExcel.write(filepath, DemoData.class).inMemory(Boolean.TRUE)
.registerWriteHandler(new CommentWriteHandler(commentList, "xlsx"))
.sheet(sheetName).doWrite(demoDataList);
System.out.println("批注模板完成,模板地址:" + filepath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 源码地址
测试源码地址:https://gitee.com/rjzhu/opencode/tree/master/easyexcel



