Java实现Excel数据操作和导入导出

本文最后更新于:May 13, 2023 pm

Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。

目录

今天偶然看见一篇博文,写的是关于SpringBoot实现Excel导入导出。因为自己不会,然后就专门花了点时间学了一下。

1.POI

1.0 导入环境依赖

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
<dependencies>
<!-- excel 03版-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15-beta2</version>
</dependency>
<!-- excel 07版-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15-beta2</version>
</dependency>
<!-- 日期格式化-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>

1.1 POI常用的包

HSSF - 提供读写Microsoft Excel XLS格式档案的功能。(03版的Excel以.xls结尾)

XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能。(07版的Excel以.xlsx结尾)

HWPF - 提供读写Microsoft Word DOC格式档案的功能。

HSLF - 提供读写Microsoft PowerPoint格式档案的功能。

HDGF - 提供读Microsoft Visio格式档案的功能。

HPBF - 提供读Microsoft Publisher格式档案的功能。

HSMF - 提供读Microsoft Outlook格式档案的功能。

1.1.1 常用类和方法

1.2 POI实现写入Excel

首先需要明白下面这张图。

其中哪儿是工作簿、哪儿是工作表。应该不会有人看不懂。

Excel03版、和Excel07版的区别是:03版最多有65536条数据,而07版无限制。

实现步骤

  1. 先建工作簿。
  2. 通过工作簿,再建工作表。
  3. 通过工作表,建行。
  4. 通过行,建单元格(列)。
  5. 给单元格赋值。
  6. 通过流的方式生成表并输出。
  7. 关闭流。

1.2.1 Excel03版

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
package com.tothefor;

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.jupiter.api.Test;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Date;

/**
* @Author DragonOne
* @Date 2021/10/19 14:19
*/
public class ExcelWriteTest {
String PATH = "/Users/dragonone/IdeaProjects/JAVAStudy/poi";

@SuppressWarnings("all")
@Test
public void excelWrite03() throws Exception {
//1.创建工作簿
Workbook workbook = new HSSFWorkbook();
//2.创建工作表,默认为正常建表中的Sheet1、Sheet2等等。也可以自定义取名称,这里以tothefor为例。
Sheet sheet = workbook.createSheet("tothefor");
//3.创建行
Row row1 = sheet.createRow(0);//第一行
//4.创建单元格(列)
Cell cell11 = row1.createCell(0); //第一行第一列的单元格
cell11.setCellValue("第一行第一列的单元格"); //设值

Cell cell12 = row1.createCell(1); //第一行第二列的单元格
cell12.setCellValue("6666666");


//第二行
Row row2 = sheet.createRow(1);
Cell cell21 = row2.createCell(0); //第二行第一列的单元格
cell21.setCellValue("第二行第一列的单元格");
Cell cell22 = row2.createCell(1); //第二行第二列的单元格
cell22.setCellValue(777777777);

Row row3 = sheet.createRow(2);
Cell cell31 = row3.createCell(0);
cell31.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));

//生成一张表(IO流)
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "excel03.xls");
//输出
workbook.write(fileOutputStream);
//关闭流
fileOutputStream.close();
System.out.println("文件输出成功!");
}
}

在相应的位置即可找到Excel文件。

1.2.2 Excel07版

其实和03版几乎一模一样,只是改一个对象而已。

1
2
Workbook workbook = new HSSFWorkbook();//03版
Workbook workbook = new XSSFWorkbook();//07版

就只需要改这一点即可,当然了,输出的格式也得从.xls改为.xlsx。

1.2.3 报错问题

在写完代码后,运行时,报了一下错误。这里也记录一下。

java: 错误: 不支持发行版本 5

首先要确保你的JDK没问题。然后看下面的图中是否对。(我在测试时用的是JDK8,所有的都是设置为了8)
File -> settings-> Build,Execution,Deployment -> Compiler -> Java Compiler

1.2.4 批量写入

直接套上双重for即可。一个for控制行,一个for控制列。不演示了。
另外,用03版的速度比较快,但数据有限制。
用07版的数据无限制,但速度慢。则需要进行优化。这时,就需要使用07版加强版。

1
2
Workbook workbook = new SXSSFWorkbook();//默认100
Workbook workbook = new SXSSFWorkbook(10000); //自定义10000条

依旧其他的都不变,只是变对象。优势在与可以写入非常大的数据量,且速度也快。但需要注意的是:过程中会产生临时文件,需要清除临时文件。默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件。当然,也可以自定义内存中的数据的数量。而且在关闭流后面还需要写清除临时文件的代码:

1
2
fileOutputStream.close();
((SXSSFWorkbook) workbook).dispose(); //清除临时文件

1.3 POI实现读入Excel

实现读入和写入其实是差不多的,步骤都是一样的,只不过是方法不同而已。具体的步骤可见读入。

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
package com.tothefor;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.sl.draw.geom.Path;
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.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

/**
* @Author DragonOne
* @Date 2021/10/19 15:49
*/
public class ExcelReadTest {
String PATH = "/Users/dragonone/IdeaProjects/JAVAStudy/poi";
@Test
public void read03T() throws Exception {
FileInputStream fileInputStream = new FileInputStream(PATH + "excel003.xlsx");
//1.得到工作簿
Workbook workbook = new XSSFWorkbook(fileInputStream);
//2.得到第几张表
Sheet sheet = workbook.getSheetAt(0);
//3.得到某行
Row row = sheet.getRow(0);
//4.得到单元格(列)
Cell cell = row.getCell(0);
System.out.println(cell.getStringCellValue()); //注意:这里需要根据不同的类型选择不同的方法,具体可见下面。
Cell cell1 = row.getCell(1);
System.out.println(cell1.getNumericCellValue()); //59.0。不再是上面写入例子中的66666,因为这个当时传进去的时候是字符串,所以这里改了一下。
fileInputStream.close();
}
}

1.3.1 读取不同的数据类型(重点)

首先,看一个小例子,用来获取一张表的首行。

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
package com.tothefor;

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.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.Test;

import java.io.FileInputStream;

/**
* @Author DragonOne
* @Date 2021/10/19 16:13
*/
public class getExcelType {
String PATH = "/Users/dragonone/IdeaProjects/JAVAStudy/poi";
@SuppressWarnings("all")
@Test
public void test() throws Exception {
FileInputStream fileInputStream = new FileInputStream(PATH + "excel003.xlsx");
//1.得到工作簿
Workbook workbook = new XSSFWorkbook(fileInputStream);
//2.得到第几张表
Sheet sheet = workbook.getSheetAt(0);
Row row = sheet.getRow(0); //获取第一行
if(row!=null){
int cnt = row.getPhysicalNumberOfCells(); //获取行的列数
for (int i = 0; i < cnt; i++) {
Cell cell = row.getCell(i);
if(cell!=null){
String value = cell.getStringCellValue();
System.out.print(value + " | ");
}
}
System.out.println();
}
}
}

前面只是获取了第一行的数据。下面就直接进行获取表的全部数据。

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
67
68
69
70
71
72
73
package com.tothefor;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
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.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.util.Date;

/**
* @Author DragonOne
* @Date 2021/10/19 16:13
*/
public class getExcelType {
String PATH = "/Users/dragonone/IdeaProjects/JAVAStudy/poi";
@SuppressWarnings("all")
@Test
public void test() throws Exception {
FileInputStream fileInputStream = new FileInputStream(PATH + "excel003.xlsx");
//1.得到工作簿
Workbook workbook = new XSSFWorkbook(fileInputStream);
//2.得到第几张表
Sheet sheet = workbook.getSheetAt(0);
int rowCount = sheet.getPhysicalNumberOfRows(); //获取表的行数
for (int i = 0; i < rowCount; i++) {
Row row = sheet.getRow(i); //得到某一行
if(row!=null){
int cloCount = row.getPhysicalNumberOfCells(); //得到列数
for (int j = 0; j < cloCount; j++) {
Cell cell = row.getCell(j); //得到某个单元格
if(cell!=null){
int typeCount = cell.getCellType(); //获取单元格的类型
switch (typeCount){
case HSSFCell.CELL_TYPE_STRING:{
String s = cell.getStringCellValue();
System.out.print(s+" | ");
break;
}
case HSSFCell.CELL_TYPE_BLANK:{
System.out.print(" ");
break;
}
case HSSFCell.CELL_TYPE_BOOLEAN:{
String s = String.valueOf(cell.getBooleanCellValue());
System.out.print(s+" | ");
break;
}
case HSSFCell.CELL_TYPE_NUMERIC:{
if(HSSFDateUtil.isCellDateFormatted(cell)){ //如果是日期类型
Date date = cell.getDateCellValue();
System.out.print(new DateTime(date).toString("yyyy-MM-dd") + " | ");
}else{
cell.setCellType(HSSFCell.CELL_TYPE_STRING); //改变类型。防止数字太长。
System.out.print(cell.getStringCellValue() + " | "); //cell.toString()也可以。
}
break;
}
}
}
}
System.out.println();
}

}
}
}

至此,基本的算过完了。其中还有一个是获取计算公式的,这里就没写了,感兴趣的可以自行了解。

2.EasyExcel

2.0新手教程

没看视频,单纯看着阿里巴巴的easyexcel官方文档照着写的一个入门案例。不想说了,中途报过n多错,而且用代码操作写出的Excel中的日期类型不知道为什么是#号,而不显示正常。但鼠标放在上面点击后在上面栏中看到的又是正常的。不知道为什么,也没解决。帖下代码吧。防止后面连这个都跑不出来。
这里附上官方文档链接:
GIthub:https://github.com/alibaba/easyexcel
https://www.yuque.com/easyexcel/doc/easyexcel

2.0.0 导入依赖

直接复制的阿里巴巴的官方pom.xml中的部分。

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.tothefor</groupId>
<artifactId>poi</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>

<!--test-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.5.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.0-beta3</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
<scope>compile</scope>
</dependency>
</dependencies>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>


</project>

2.0.1 写操作

DemoData.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.tothefor.easyexcel;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

import java.util.Date;

@Data
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}

eeTest.java

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
package com.tothefor.easyexcel;

import com.alibaba.excel.EasyExcel;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
* @Author DragonOne
* @Date 2021/10/19 20:45
*/
public class eeTest {
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;
}

@Test
public void simpleWrite() {
String PATH = "/Users/dragonone/IdeaProjects/JAVAStudy/poi";
String fileName = PATH+"pp.xlsx";
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());

}
}

2.0.2 读操作

DemoData.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.tothefor.eeRead;

import lombok.Data;

import java.util.Date;

@Data
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}

DemoDAO.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.tothefor.eeRead;

import java.util.List;

/**
* 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
**/
public class DemoDAO {
public void save(List<DemoData> list) {
// 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
}
}

DemoDataListener.java

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.tothefor.eeRead;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 3000;
/**
* 缓存的数据
*/
private List<DemoData> list = new ArrayList<>(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoDAO demoDAO;

public DemoDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDAO = new DemoDAO();
}

/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}

/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
System.out.println(JSON.toJSONString(data));
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list = new ArrayList<>(BATCH_COUNT);
}
}

/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
LOGGER.info("所有数据解析完成!");
}

/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
demoDAO.save(list);
LOGGER.info("存储数据库成功!");
}
}

eeTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.tothefor.eeRead;

import com.alibaba.excel.EasyExcel;
import org.junit.jupiter.api.Test;

/**
* @Author DragonOne
* @Date 2021/10/19 20:53
*/
public class eeTest {
@Test
public void simpleRead() {
String PATH = "/Users/dragonone/IdeaProjects/JAVAStudy/poi";
String fileName = PATH+"pp.xlsx";
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
}

后面的其他用法有机会再补吧。


本文作者: 墨水记忆
本文链接: https://tothefor.com/DragonOne/3004796258.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!