应用场景
最近的一个项目需要使用到excel导入功能。比如测试用例,根据里面的案例生成自动化测试脚本文件。那么,首先就要将excel读取程序当中。
目前操作excel比较流行的就是Apache POI和阿里巴巴的easyexcel。
Apache POI
官网: http://poi.apache.org/
POI,全称Apache POI,使用Java编写的免费开源的跨平台的Java API。 是创建和维护操作各种符合 Office Open XML(OOXML)标准和微软的 OLE 2 复合文档格式(OLE2)的 Java API。用它可以使用 Java 读取和创建, 修改 MS Excel 文件。而且,还可以使用 Java 读取和创建 MS Word 和 MSPowerPoint 文件。Apache POI 提供 Java 操作 Excel 解决方案(适用于 Excel97-2008)。
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
| <dependencies>
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.11</version> </dependency>
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.11</version> </dependency>
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.1</version> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> </dependency> </dependencies>
|
03版本与07版本的区别:03版是以xls结尾, 07版以xlsx结尾。而且03班最多支持65536行数据,而07版没有限制。
在一个excel文件里面,有工作表、工作簿、行、列。
所以我们只需要找到这几个对象就可以开始操作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
| String Path = "F:\\java\\excel\\poiexcel";
@Test public void testWrite03() throws Exception { Workbook workbook = new HSSFWorkbook(); Sheet sheet = workbook.createSheet("测试案例"); Row row1 = sheet.createRow(0); Cell cell11 = row1.createCell(0); cell11.setCellValue("案例描述");
Cell cell12 = row1.createCell(1); cell12.setCellValue("案例名称");
Cell cell13 = row1.createCell(2); cell13.setCellValue("预期结果");
Cell cell14 = row1.createCell(3); cell14.setCellValue("GIVEN");
Cell cell15 = row1.createCell(4); cell15.setCellValue("WHEN");
Cell cell16 = row1.createCell(5); cell16.setCellValue("THEN"); FileOutputStream fileOutputStream = new FileOutputStream(Path+"测试案例.xls"); workbook.write(fileOutputStream); fileOutputStream.close(); System.out.println("生成成功!");
}
|
对于07版本的,只要在上面的代码,将下面代码
1 2
| Workbook workbook = new HSSFWorkbook();
|
改为
1 2
| Workbook workbook = new XSSFWorkbook();
|
然后文件的后缀是xlsx。
大文件写入
03版: 大文件写HSSF
缺点:最多只能处理65536行数据。否则会抛出异常。
优点: 过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度比较快。
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
| @Test public void testWrite03BigData() throws Exception { Workbook workbook = new HSSFWorkbook(); Sheet sheet = workbook.createSheet("bigdata03");
long begin = System.currentTimeMillis(); for (int i = 0; i < 65536; i++) { Row row = sheet.createRow(i); for(int j = 0; j < 10; j++){ Cell cell = row.createCell(j); cell.setCellValue("a"+j); }
}
FileOutputStream fileOutputStream = new FileOutputStream(Path+"bigdata03.xls"); workbook.write(fileOutputStream); fileOutputStream.close();
long end = System.currentTimeMillis(); System.out.println((double)(end-begin)/1000);
System.out.println("成功!");
}
|
1.577
成功!
将65536 改为65537, 报异常
07版: 大文件写XSSF
缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出。如一百万条。
优点:可以写较大的数据量。比如说20万条。
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
| @Test public void testWrite07BigData() throws Exception { Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("bigdata07");
long begin = System.currentTimeMillis(); for (int i = 0; i < 65536; i++) { Row row = sheet.createRow(i); for(int j = 0; j < 10; j++){ Cell cell = row.createCell(j); cell.setCellValue("a"+j); }
}
FileOutputStream fileOutputStream = new FileOutputStream(Path+"bigdata07.xlsx"); workbook.write(fileOutputStream); fileOutputStream.close();
long end = System.currentTimeMillis(); System.out.println((double)(end-begin)/1000);
System.out.println("成功!");
}
|
12.815
成功!
他可以写大量的数据,没有65536 的限制,比如写100000行数据
17.968
成功!
可以看到XSSF的速度极慢!下面可以改进,通过SXSSF
大文件写SXSSF
优点: 可以写非常大的数据量,如一百万条,甚至更多条。写数据速度快,占用更少的内存。
但是在过程中会产生临时文件,需要清理临时文件。默认由100条记录被保存到内存中。如果超过这数量,则最前面的数据被写入临时文件。如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook(数量)。
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
| @Test public void testWrite07BigDataS() throws Exception { SXSSFWorkbook workbook = new SXSSFWorkbook(); Sheet sheet = workbook.createSheet("bigdataSSS07");
long begin = System.currentTimeMillis(); for (int i = 0; i < 65536; i++) { Row row = sheet.createRow(i); for(int j = 0; j < 10; j++){ Cell cell = row.createCell(j); cell.setCellValue("a"+j); }
}
FileOutputStream fileOutputStream = new FileOutputStream(Path+"bigdata07S.xlsx"); workbook.write(fileOutputStream); fileOutputStream.close(); workbook.dispose();
long end = System.currentTimeMillis(); System.out.println((double)(end-begin)/1000);
System.out.println("成功!");
}
|
1.17
成功!
即使写入100000条记录
1.719
成功!
官方解释。实现big green demo策略的流逝版本,这允许写入非常大的文件,而不会耗尽内存,因为任何时候只有可配置的行部分。被保存在内存中。请注意。仍然可能会消耗大量内存。这些内存基于您正在使用的功能,例如合并区域、注释…….仍然只存在内存中。因此,如果广泛使用,可能需要大量内存。
POI 读取Excel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class excelReadTest { String Path = "F:\\java\\excel\\poiexcel";
@Test public void testRead() throws Exception {
FileInputStream in = new FileInputStream(Path+"测试案例.xls");
Workbook workbook = new HSSFWorkbook(in); Sheet sheet = workbook.getSheetAt(0); Row row = sheet.getRow(0); for(int i = 0; i < 6; i++){ Cell cell = row.getCell(i); String stringCellValue = cell.getStringCellValue(); System.out.println(stringCellValue); } }
}
|
案例描述
案例名称
预期结果
GIVEN
WHEN
THEN
读取不同的数据类型
里面包含了字符串、数字、日期等数据类型。
看几个API
方法 | 描述 |
---|
Row.java –> int getPhysicalNumberOfCells(); | 获取当前行的单元格数量 |
Cell.java –> int getCellType(); | 获取当前单元格的类型,是一个枚举类型。 CELL_TYPE_BLANK CELL_TYPE_NUMERIC CELL_TYPE_STRING CELL_TYPE_FORMULA CELL_TYPE_BOOLEAN CELL_TYPE_ERROR |
Sheet.java – > int getPhysicalNumberOfRows(); | 获取工作表中的行数 |
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
| @Test public void testRead07() throws Exception {
FileInputStream in = new FileInputStream(Path+"明细.xlsx");
Workbook workbook = new XSSFWorkbook(in); Sheet sheet = workbook.getSheetAt(0);
Row rowTitle = sheet.getRow(0); if(rowTitle != null){ int cells = rowTitle.getPhysicalNumberOfCells(); for (int i = 0; i < cells; i++) { Cell cell = rowTitle.getCell(i); if(cell != null){ int cellType = cell.getCellType(); String stringCellValue = cell.getStringCellValue(); System.out.print(stringCellValue + " | "); } } System.out.println();
}
int rows = sheet.getPhysicalNumberOfRows(); for (int i = 1; i < rows; i++) { Row rowData = sheet.getRow(i); if(rowData != null){ int cells = rowData.getPhysicalNumberOfCells(); for (int j = 0; j < cells; j++) { Cell cell = rowData.getCell(j); if(cell != null){ int cellType = cell.getCellType(); String cellValue = null; switch (cellType){ case XSSFCell.CELL_TYPE_STRING:
cellValue = cell.getStringCellValue(); break; case XSSFCell.CELL_TYPE_BOOLEAN:
cellValue = String.valueOf(cell.getBooleanCellValue()); break;
case XSSFCell.CELL_TYPE_NUMERIC:
if(HSSFDateUtil.isCellDateFormatted(cell)){ Date date = cell.getDateCellValue(); cellValue = new DateTime(date).toString("yyyy-MM-dd"); }else{ cell.setCellType(XSSFCell.CELL_TYPE_STRING); cellValue = cell.getStringCellValue() ; }
break; case XSSFCell.CELL_TYPE_BLANK:
break; case XSSFCell.CELL_TYPE_ERROR: break;
} System.out.print(cellValue+" ");
}
} } System.out.println(); }
in.close();
}
|
PIO 这个框架比较原生,用起来比较麻烦,而且在文件很大的时候可能会出现OOM异常,因为它导入是将整个文档全部导入到内存中。
所以, easye小说xcel就出现了,它解决了POI的问题,而且使用起来也非常方便。
easyExcel
官网: https://www.yuque.com/easyexcel/doc/easyexcel
github: https://github.com/alibaba/easyexcel
easyexcel是阿里巴巴开源的一个Excel处理框架。以使用简单,节省内存著称。
Easy Excel能大大减少占用内存的主要原因是在解析Excel时,没有将文件数据一次性全部加载到内存中。而是在磁盘上一行行读取逐个解析。
easyexcel 是一个 JAVA 解析 Excel 工具。Java 解析、生成 Excel 比较有名的框架有 Apache poi、jxl 。但他们都存在一个严重的问题就是非常的耗内存,poi 有一套 SAX 模式的 API 可以一定程度的解决一些内存溢出的问题,但 POI 还是有一些缺陷,比如 07 版 Excel 解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel 重写了 poi 对 07 版 Excel 的解析,能够原本一个 3M 的 excel 用 POI sax 依然需要 100M 左右内存降低到 KB 级别,并且再大的 excel 不会出现内存溢出,03 版依赖 POI 的 sax 模式。在上层做了模型转换的封装,让使用者更加简单方便。
导入依赖
1 2 3 4 5 6 7
| <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.6</version> </dependency>
|
实体类
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
| package cn.hongliang;
import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data;
import java.util.Date;
@Data public class EquivalenceImportEntity {
@ExcelProperty("案例描述") private String caseDescription;
@ExcelProperty("案例名称") private String caseName;
@ExcelProperty("预期结果") private String expectedResult;
@ExcelProperty("GIVEN") private String given; @ExcelProperty("WHEN") private String when; @ExcelProperty("THEN") private String then;
@ExcelIgnore private String packageDir;
@ExcelIgnore private String classFileName;
@ExcelProperty("创建时间") private Date createDateTime;
}
|
写Excel
通用数据生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private List<EquivalenceImportEntity> data() { List<EquivalenceImportEntity> list = new ArrayList<EquivalenceImportEntity>(); for (int i = 0; i < 10; i++) { EquivalenceImportEntity data = new EquivalenceImportEntity(); data.setCaseDescription("用于测试easyExcel的案例" + i); data.setCaseName("写excel" + i); data.setGiven("我付款1000RMB预定了一个3周后从成都飞往三亚的航班。" + i); data.setWhen("在航班起飞前一周“我”取消了该行程"); data.setThen("我应该得到预定机票半价的退款(500RMB)" + i); data.setExpectedResult("返回500元 " + i); data.setCreateDateTime(new Date()); list.add(data); } return list; }
|
写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@Test public void simpleWrite() { String fileName = Path + System.currentTimeMillis() + ".xlsx"; EasyExcel.write(fileName, EquivalenceImportEntity.class).sheet("User").doWrite(data());
}
|
也可以排除指定的列去写入,将排除的列存入一个set中,然后write的时候传入set就行了。具体可参照官网 https://www.yuque.com/easyexcel/doc/write
读取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 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
| package cn.hongliang;
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;
public class CaseDataListener extends AnalysisEventListener<EquivalenceImportEntity> {
private static final Logger LOGGER = LoggerFactory.getLogger(CaseDataListener.class);
private static final int BATCH_COUNT = 5; List<EquivalenceImportEntity> list = new ArrayList<EquivalenceImportEntity>(); public CaseDataListener() {
}
@Override public void invoke(EquivalenceImportEntity data, AnalysisContext context) { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); if (list.size() >= BATCH_COUNT) { saveData(); list.clear(); } }
@Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(); LOGGER.info("所有数据解析完成!"); }
private void saveData() { LOGGER.info("{}条数据,开始存储数据库!", list.size());
LOGGER.info("存储数据库成功!"); }
}
|
读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@Test public void simpleRead() { String fileName = Path+ "1596512468069.xlsx"; EasyExcel.read(fileName, EquivalenceImportEntity.class, new CaseDataListener()).sheet().doRead();
}
|
这样就把Excel文件的内容读取到了!!!