文章目录

  • ​​一、简介​​
  • ​​1.1、POI​​
  • ​​1.2、EasyExcel​​
  • ​​1.3、对比​​
  • ​​二、POI​​
  • ​​2.1、03版本写入​​
  • ​​2.2、07版本写入​​
  • ​​2.3、批量写入​​
  • ​​2.4、普通读取​​
  • ​​2.5、不同类型的数据读取​​



在我们的项目中,经常会使用项目导入和导出的功能。本文以操作Excel为例,目前比较流行的是Apache POI和阿里巴巴的EasyExcel。本文只讲了POI,EasyExcel的学习可以参考对应的​​官方文档​


一、简介

1.1、POI

​Apache POI官网​

Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。(来源百度百科)

基本结构:

  • HSSF - 提供读写Microsoft Excel格式档案的功能。(07版本之前,如03)
  • XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。(07版本及之后,如07)
  • HWPF - 提供读写Microsoft Word格式档案的功能。
  • HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
  • HDGF - 提供读写Microsoft Visio格式档案的功能。


1.2、EasyExcel

​easyExcel文档​

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便(来源GitHub的README)


1.3、对比

EasyExcel是阿里巴巴开源的一个Excel处理框架,特点是使用简单、节省内存。

区别于POI,EasyExcel能大大减少内存的占用,主要原因是它在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行一行的读取数据,再依次解析。

下图是对应的解析流程图(来源yuque官网):

Apache POI简单入门_后缀



二、POI

在使用POI之前,我们需要明确Java一切皆对象,我们如何通过对象定位到对应每一个单元格上呢?



1、我们首先需要实例化一个工作簿对象,即我们的整个文件

Apache POI简单入门_数据_02


2、在整个文件下面,我们需要定位到一个工作表,所以我们还需要实例化一个工作表

Apache POI简单入门_数据_03



3、在一个工作表中,我们需要定位每一行,即还需要实例化一个每一行的对象

Apache POI简单入门_后缀_04



4、接下来,就只剩下我们对应的列了,即实例化一个列对象

Apache POI简单入门_System_05


5、最终对定位到的单元格,进行一个填值操作


2.1、03版本写入

最开始我使用的poi版本为3.9。

在03版本的excel写入时一切正常,但是切换到07版本时就出现了下面的这个问题。

Apache POI简单入门_System_06



网上有说是缺少xmlbeans这个jar包,我也去下过,但是还是不好使。最后我将poi的版本改为3.14,就没有出现那个问题了。

1、导入pom依赖

<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>


2、测试代码

public class PoiTest01 {

private String path = "E:\\Softwareworkspace\\ideaworkspace\\studyJava";

@Test
public void test03() throws Exception {
//1.创建一个工作簿
Workbook workbook = new HSSFWorkbook();

//2.创建一个工作页,不指定名字使用默认名字
Sheet sheet = workbook.createSheet();

//3.创建一行数据
Row row0 = sheet.createRow(0);

//4.定位到一行中的每一列
Cell cell00 = row0.createCell(0);

//5.给定位到的单元格设置值
cell00.setCellValue("姓名");

Cell cell01 = row0.createCell(1);
cell01.setCellValue("mobian");


//定位到第2行的1、2两列,并设置值
Row row1 = sheet.createRow(1);
row1.createCell(0).setCellValue("性别");
row1.createCell(1).setCellValue("男");


//6.通过IO形式,将添加的内容输出到对应文件
FileOutputStream file = new FileOutputStream(path + "个人信息.xls");
workbook.write(file);

file.close();
System.out.println("输出完毕");
}
}

即在我们指定的目录下,生成了指定文件名的文件。

Apache POI简单入门_System_07


2.2、07版本写入

07版本与03版本使用方法类似。07版本文件后缀为xlsx,03版本后缀为xls,即我们也能想到,区别就在于我们实例化的工作簿对象。

1、导入pom依赖

<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>

<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>


2、测试代码

public class PoiTest01 {

private String path = "E:\\Softwareworkspace\\ideaworkspace\\studyJava";

@Test
public void test03() throws Exception {
//1.创建一个工作簿
Workbook workbook = new XSSFWorkbook();

//2.创建一个工作页
Sheet sheet = workbook.createSheet("07版本测试页");

//3.创建一行数据
Row row0 = sheet.createRow(0);

//4.定位到一行中的每一列
Cell cell00 = row0.createCell(0);

//5.给定位到的单元格设置值
cell00.setCellValue("姓名");

Cell cell01 = row0.createCell(1);
cell01.setCellValue("mobian");


//定位到第2行的1、2两列,并设置值
Row row1 = sheet.createRow(1);
row1.createCell(0).setCellValue("性别");
row1.createCell(1).setCellValue("男");


//6.通过IO形式,将添加的内容输出到对应文件
FileOutputStream file = new FileOutputStream(path + "个人信息.xlsx");
workbook.write(file);

file.close();
System.out.println("输出完毕");
}
}

在03版本上,由于我们是面向接口编程,所以我们只需要修改最初的工作簿对象,即XSSFWorkbook。并且将修改的文件后缀改为xlsx即可。


2.3、批量写入

在真实的项目中,我们的文件数据写入肯定就不止这么点数据,就涉及到大批量的数据。

03版本与07版本的一个区别就是03版本文件只能存取65536行

Apache POI简单入门_数据_08

当我们的文件行数超过这个数字,就会抛出异常。

此时我们就需要使用07版本的文件后缀。

但使用xlsx后缀写入时,由于其写入特点,写入时的速度明显要低于xls的写入。

我们此时就要借助SXSSF

大文件写入

可以写入非常大的数据,如100W条,写入速度更快,占用内存更少

注意:

  • 写入过程会产生临时文件,需要清理临时文件
  • 默认有100条几率被保存在内存中,如果超过这个数量,则最前面的数据会被写入临时文件
  • 需要自定义内存中数据的数量,则在构造方法中,传入对应的数量即可


测试代码

@Test
public void BigDataTest01() throws Exception {

long start = System.currentTimeMillis();
Workbook workbook = new SXSSFWorkbook();
Sheet sheet = workbook.createSheet();

for (int i = 0; i < 1000000; i++) {
Row row = sheet.createRow(i);
for (int j = 0; j < 10; j++) {
row.createCell(j).setCellValue(j);
}
}

FileOutputStream file = new FileOutputStream(path + "大批量数据测试.xlsx");
workbook.write(file);
file.close();
((SXSSFWorkbook) workbook).dispose();

long end = System.currentTimeMillis();
System.out.println("花费时间:" + (end - start));

}
花费时间:13854



实现“BigGridDemo”策略的流式SXSSFWorkbook版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能。例如合并区域,注释…仍然只存储在内存中,因此如果广泛使用,可能需要大量内存



结论:

  • 使用xls后缀(HSSFWorkbook)的文件写入会非常快,但是存储数量有限(写入缓存,不操作磁盘,最后一次写入磁盘)
  • 使用xlsx后缀(XSSFWorkbook)的文件写入不快,但是理论上能无限存
  • 希望又能写入大量数据,又快,那就需要使用 SXSSFWorkbook 对象


2.4、普通读取

测试代码

@Test
public void test01() throws Exception {
FileInputStream fileInputStream = new FileInputStream(path+"个人信息.xls");

Workbook sheets = new HSSFWorkbook(fileInputStream);
Sheet sheet = sheets.getSheetAt(0);

Row row = sheet.getRow(0);
System.out.println(row.getCell(0).getStringCellValue());
System.out.println(row.getCell(1).getStringCellValue());


Row row1 = sheet.getRow(1);
System.out.println(row1.getCell(0).getStringCellValue());
System.out.println(row1.getCell(1).getNumericCellValue());

fileInputStream.close();
}

结果:

Apache POI简单入门_数据_09

理解了前面的写入,这里的读取也就十分好理解了。

写入时,我们都是createXX;读取时,我们都是getXXX


07版本

修改不大,只需要对应的对象以及输入流的文件后缀即可

...
FileInputStream fileInputStream = new FileInputStream(path+"个人信息.xlsx");
...
Workbook sheets = new XSSFWorkbook(fileInputStream);
...


2.5、不同类型的数据读取

基本类型读取

我们在获取数据内容时,需要根据对应excel文档中的数据类型进行判断,防止typeMismatch异常。

Apache POI简单入门_后缀_10

测试案例(07版本示例)

@Test
public void test02() throws Exception {
FileInputStream fileInputStream = new FileInputStream(path + "个人信息.xlsx");

Workbook sheets = new XSSFWorkbook(fileInputStream);
Sheet sheet = sheets.getSheetAt(0);
Row rowTil = sheet.getRow(0);
//打印输出标题部分内容
if (rowTil != null) {
int cellsTilCount = rowTil.getPhysicalNumberOfCells();
for (int i = 0; i < cellsTilCount; i++) {
System.out.print(rowTil.getCell(i).getStringCellValue() + " | ");

}
System.out.println();
}
int rowsCount = sheet.getPhysicalNumberOfRows();
for (int i = 1; i < rowsCount; i++) {
//获取每一行数据
Row rowCon = sheet.getRow(i);
if (rowCon != null) {
int cellsConCount = rowCon.getPhysicalNumberOfCells();
for (int j = 0; j < cellsConCount; j++) {
//获取每一个单元格的数据
Cell cell = rowCon.getCell(j);

//获取类型,并判断
int cellType = cell.getCellType();
switch (cellType) {
case XSSFCell.CELL_TYPE_NUMERIC:
//如果是日期类型就输出为日期格式,如果不是就输出为字符串格式
if (DateUtil.isCellDateFormatted(cell)) {
System.out.print(cell.getDateCellValue() + " | ");
} else {
cell.setCellType(XSSFCell.CELL_TYPE_STRING);
System.out.print(cell.getStringCellValue() + " | ");
}
break;
//公式类型
case XSSFCell.CELL_TYPE_FORMULA:
System.out.print(cell.getCellFormula() + " | ");
break;
case XSSFCell.CELL_TYPE_STRING:
System.out.print(cell.getStringCellValue() + " | ");
break;
case XSSFCell.CELL_TYPE_BOOLEAN:
System.out.print(cell.getBooleanCellValue() + " | ");
break;
case XSSFCell.CELL_TYPE_BLANK:
System.out.print(" | ");
break;
case XSSFCell.CELL_TYPE_ERROR:
System.out.print(cell.getErrorCellValue() + " | ");
break;
}
}
}
}
fileInputStream.close();
}


公式类型读取

测试案例(07版本示例)

public void testForMula() throws Exception {
FileInputStream fileInputStream = new FileInputStream(path + "个人信息.xlsx");

Workbook sheets = new XSSFWorkbook(fileInputStream);
Sheet sheet = sheets.getSheetAt(0);

//公式行为第3行
Row rowMula = sheet.getRow(3);
Cell cellMula = rowMula.getCell(0);

//执行公式接口
FormulaEvaluator formulaEvaluator = new XSSFFormulaEvaluator((XSSFWorkbook) sheets);

int cellType = cellMula.getCellType();
switch (cellType) {
case Cell.CELL_TYPE_FORMULA:
System.out.println("对应的公式类型为:"+cellMula.getCellFormula());

//执行对应的公式,并按照字符串格式输出
CellValue evaluate = formulaEvaluator.evaluate(cellMula);
System.out.print(evaluate.formatAsString());
}
}