【POI&EasyExcel】POI及EasyExcel基础教程
前言:
推荐免费POI及EasyExcel视频:【狂神说Java】POI及EasyExcel一小时搞定_哔哩哔哩_bilibili
笔记代码下载地址:
蓝奏云:下载地址 密码:joker
百度云:下载地址 提取码:uazm
POI和EasyExcel
Apache POI
Apache POI 是由 Apache 基金会提供的一个 API,它是不同 java 库的集合。这些库提供了读取、写入和操作不同微软文件的功能,如 excel 工作表、power-point 和 word 文件。
Apache POI - Download Release Artifacts
基本功能
- HSSF-提供读写
MicrosoftExcel
格式档案的功能 - HSSF-提供读写
MicrosoftExcel OOXML
格式档案的功能 - HWPF-提供读写
MicrosoftWord
格式档案的功能 - HSLF-提供读写
MicrosoftPowerPoint
格式档案的功能 - HDGF-提供读写
MicrosoftVisio
格式档案的功能
EasyExcel
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
alibaba/easyexcel: 快速、简洁、解决大文件内存溢出的java处理Excel工具 (github.com)
应用场景
-
将用户信息导出为Excel表格
-
将Excel表中的信息录入到网站数据库
Excel基本写操作
POI-Excel写
-
创建一个空项目,空项目下创建
Maven Module
为POI
-
引入
pom
依赖1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<!-- 引入依赖-->
<dependencies>
<!-- xls(03)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<!-- xlsx(07)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
<!-- 日期格式化工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
<!-- junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>03|07版本写入,就是对象不同,方法一样的。
03版本最多只有65535行
-
Excel主要对象
-
工作簿:Workbook
-
工作表:Sheet
-
行:Row
-
列(单元格):Cell
-
-
Excel基本写测试
03版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53package com.jokerdig;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.joda.time.DateTime;
import org.junit.Test;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author Joker大雄
* @data 2022/8/30 - 10:34
**/
public class ExcelWriteTest {
// 存放路径
String PATH= "D:\\Project\\project3\\POI&EasyExcel\\excel\\";
// 03版本的excel
public void testWrite03() throws IOException {
// 1. 创建工作簿
Workbook workbook = new HSSFWorkbook();
// 2. 创建工作表
Sheet sheet = workbook.createSheet("学习统计表");
// 3. 创建第一行
Row row1 = sheet.createRow(0);
// 4. 创建单元格
Cell cell1_1 = row1.createCell(0);
// 5. 写入值
cell1_1.setCellValue("今日学习时间");
// 第二个单元格
Cell cell1_2 = row1.createCell(1);
// 时间工具类
String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell1_2.setCellValue(time);
// 创建第二行
Row row2 = sheet.createRow(1);
Cell cell2_1 = row2.createCell(0);
cell2_1.setCellValue("今日学习进度");
Cell cell2_2 = row2.createCell(1);
cell2_2.setCellValue("60%");
// 6. 生成Excel表 03版本使用.xls
FileOutputStream outputStream = new FileOutputStream(PATH + "学习信息统计03.xls");
workbook.write(outputStream);
// 关闭流
outputStream.close();
System.out.println("03Excel生成完毕");
}
}运行结果
1
2
303Excel生成完毕
Process finished with exit code 007版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// 07版本的excel
public void testWrite07() throws IOException {
// 1. 创建工作簿
Workbook workbook = new XSSFWorkbook();
// 2. 创建工作表
Sheet sheet = workbook.createSheet("学习统计表");
// 3. 创建第一行
Row row1 = sheet.createRow(0);
// 4. 创建单元格
Cell cell1_1 = row1.createCell(0);
// 5. 写入值
cell1_1.setCellValue("今日学习时间");
// 第二个单元格
Cell cell1_2 = row1.createCell(1);
// 时间工具类
String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell1_2.setCellValue(time);
// 创建第二行
Row row2 = sheet.createRow(1);
Cell cell2_1 = row2.createCell(0);
cell2_1.setCellValue("今日学习进度");
Cell cell2_2 = row2.createCell(1);
cell2_2.setCellValue("60%");
// 6. 生成Excel表 07版本使用.xlsx
FileOutputStream outputStream = new FileOutputStream(PATH + "学习信息统计07.xlsx");
workbook.write(outputStream);
// 关闭流
outputStream.close();
System.out.println("07Excel生成完毕");
}运行结果
1
2
307Excel生成完毕
Process finished with exit code 0
大数据量写入
大文件写HSSF
缺点:最多只能处理65536行,否则会抛出异常!
java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)
优点:过程中写入缓存,不操作磁盘,最后一次写入磁盘,速度快!
1 | // 大文件写03 |
运行结果
1 | 数据写入完成 |
大文件写XSSF
缺点:写数据速度满,非常好内存,也会发生内存移除,如写入100w条数据。
优点:可以写较大的数据量,如20w条。
1 | // 大文件写07 |
运行结果
1 | 数据写入完成 |
大文件写SXSSF
优点:可以写非常大的数据量,如100w条甚至更多,写数据速度快,占用内存少!
1 | // 大文件写07加速 |
运行结果
1 | 数据写入完成 |
Excel基本读取及注意点
03读取
1 | package com.jokerdig; |
运行结果
1 | 今日学习时间 |
07读取
1 | // 07版本读excel |
运行结果
1 | 今日学习时间 |
读取不同类型的数据
读取文件下载:
下载地址:百度云 提取码:xssn
1 | // 读取不同类型的数据 |
运行结果
1 | 编号 | 卡号 | 持卡人 | 手机号 | 消费日期 | 小票号 | 商品编号 | 商品条码 | 商品名称 | 商品单位 | 原价 | 销售价 | 销售数量 | 销售金额 | 优惠金额 | 是否上架 | |
了解计算公式
1 |
|
运行结果
1 | SUM(A2:A4) |
EasyExcel使用
官方文档:EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel (alibaba.com)
-
新建
module
为EasyExcel
-
导入pom依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency> -
测试写
新建包
com.jokerdig.easyExcel
,在包下新建DemoData
类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30package com.jokerdig.easyExcel;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @author Joker大雄
* @data 2022/8/30 - 15:01
**/
public class DemoData {
private String string;
private Date date;
private Double doubleData;
/**
* 忽略这个字段
*/
private String ignore;
}EasyTest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52package com.jokerdig.easyExcel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import org.junit.Test;
import java.util.Date;
import java.util.List;
/**
* @author Joker大雄
* @data 2022/8/30 - 15:08
**/
public class EasyTest {
// 存放路径
String PATH= "D:\\Project\\project3\\POI&EasyExcel\\excel\\";
private List<DemoData> data() {
List<DemoData> list = ListUtils.newArrayList();
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;
}
public void simpleWrite() {
// 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入
// 写法1 JDK8+
// since: 3.0.0-beta1
String fileName = PATH+"WriteTest1.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoData.class)
.sheet("模板")
.doWrite(() -> {
// 分页查询数据
return data();
});
// 写法2
fileName =PATH+"WriteTest2.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
} -
运行结果
-
测试读
DemoDAO
1
2
3
4
5
6
7
8
9
10
11
12package com.jokerdig.easyExcel;
import java.util.List;
/**
* 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
**/
public class DemoDAO {
public void save(List<DemoData> list) {
// 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
}
}DemoDataListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67package com.jokerdig.easyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener implements ReadListener<DemoData> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
/**
* 缓存的数据
*/
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoDAO demoDAO;
public DemoDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDAO = new DemoDAO();
}
// 读取数据会执行 invoke 方法
// DemoData 类型
// AnalysisContext 分析上文
public void invoke(DemoData data, AnalysisContext context) {
System.out.println(JSON.toJSONString(data));
cachedDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData(); // 持久化
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
demoDAO.save(cachedDataList);
log.info("存储数据库成功!");
}
}EasyTest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 读
public void simpleRead() {
// 写法1:JDK8+ ,不用额外写一个DemoDataListener
// since: 3.0.0-beta1
String fileName = PATH+"WriteTest1.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
// 这里每次会读取100条数据 然后返回过来 直接调用使用数据就行
System.out.println("============================方法1========================");
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
for (DemoData demoData : dataList) {
System.out.println(JSON.toJSONString(demoData));;
}
})).sheet().doRead();
// 写法2
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
fileName = PATH+"WriteTest2.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
System.out.println("============================方法2========================");
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
} -
运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27============================方法1========================
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
{"date":1661843837623,"doubleData":0.56,"string":"字符串0"}
{"date":1661843837623,"doubleData":0.56,"string":"字符串1"}
{"date":1661843837623,"doubleData":0.56,"string":"字符串2"}
{"date":1661843837623,"doubleData":0.56,"string":"字符串3"}
{"date":1661843837623,"doubleData":0.56,"string":"字符串4"}
{"date":1661843837623,"doubleData":0.56,"string":"字符串5"}
{"date":1661843837623,"doubleData":0.56,"string":"字符串6"}
{"date":1661843837623,"doubleData":0.56,"string":"字符串7"}
{"date":1661843837623,"doubleData":0.56,"string":"字符串8"}
{"date":1661843837623,"doubleData":0.56,"string":"字符串9"}
============================方法2========================
{"date":1661843838122,"doubleData":0.56,"string":"字符串0"}
{"date":1661843838122,"doubleData":0.56,"string":"字符串1"}
{"date":1661843838122,"doubleData":0.56,"string":"字符串2"}
{"date":1661843838122,"doubleData":0.56,"string":"字符串3"}
{"date":1661843838122,"doubleData":0.56,"string":"字符串4"}
{"date":1661843838122,"doubleData":0.56,"string":"字符串5"}
{"date":1661843838122,"doubleData":0.56,"string":"字符串6"}
{"date":1661843838122,"doubleData":0.56,"string":"字符串7"}
{"date":1661843838122,"doubleData":0.56,"string":"字符串8"}
{"date":1661843838122,"doubleData":0.56,"string":"字符串9"}
Process finished with exit code 0 -
固定写法
-
写入,固定类格式进行写入
-
读取,根据监听器设置的规则进行读取
-