一、什么是POI
Apache POI 是用java编写的免费开源的JAVA API,Apach POI提供了API给Java 程式对 Microsoft Office(Excel、WORD、PowerPoint、Visio 等,主要实现用于 Excel)格式档案读和写的功能,POI 为 “ Poor Obfuscation Implementation ” 的首字母缩写,意为“简洁版的模糊实现”。
简单来说就是使用java来对excel文件进行读写操作,POI提供了java操作excel等文件的API。
二、Apache POI的使用
- 导入相应依赖,因为excel有两个版本,一个是03版本.xls,一个是07版本.xlsx:
<!--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>
- 进行相关API测试,主要分为对Excel文件的读和写,全在代码里了
public class ExcelTest {
// 记录下项目根路径,为方便后面拼接文件路径
String Path = "D:\\IdeaProjects\\testdemo\\poi_demo\\";
@Test //03版本,写入测试
public void testWrite03() throws Exception{
// 1. 创建工作簿(也就是excel文件)
Workbook workbook = new HSSFWorkbook(); //03版本的excel,03版本最大支持65536行数据 xxx.xls
//Workbook workbook = new XSSFWorkbook(); // 07版本的excel,07版本没有限制 xxx.xlsx
// 2. 创建表(也就是excel文件中左下角的sheet表)
Sheet sheet = workbook.createSheet("统计表01");
// 3. 根据表,创建行
Row row = sheet.createRow(0);
// 4. 根据行,创建单元格 (其实就是坐标,注意一定是先有行,再有列。根据行创建唯一的单元格)
Cell cell1 = row.createCell(0);
cell1.setCellValue("球队");
Cell cell2 = row.createCell(1);
cell2.setCellValue("球员");
// 重复上的操作,再创建一行
Row row1 = sheet.createRow(1);
Cell cell = row1.createCell(0);
cell.setCellValue("太阳");
Cell cell3 = row1.createCell(1);
cell3.setCellValue("保罗");
// 新建文件输出流
FileOutputStream fos = new FileOutputStream(Path + "NBA统计表.xls");
// 将设置好的工作簿,写入磁盘
workbook.write(fos);
fos.close();//关闭流
System.out.println("文件生成成功!");
}
@Test // 07版本,写入测试
public void testWrite07BigData() throws Exception{
long begin = System.currentTimeMillis();
//Workbook workbook = new XSSFWorkbook();// 创建07版本的Excel
Workbook workbook = new SXSSFWorkbook(); // 创建07版本的快速写入实现类
Sheet sheet = workbook.createSheet();
for (int i = 0; i < 100000 ; i++) { // 07版本行数无限制,所以创建个大于65536行的表
Row row = sheet.createRow(i);
for (int j = 0; j < 5; j++) { // 每行写5个数据
Cell cell = row.createCell(j);
cell.setCellValue(i+","+j);
}
}
FileOutputStream fos = new FileOutputStream(Path+"观众数量统计表.xlsx");
workbook.write(fos);
fos.close();
// 清除临时文件
((SXSSFWorkbook)workbook).dispose();
long end = System.currentTimeMillis();
// 经测试发现:
// 普通07版本XSSFWorkbook,耗时50秒
// 快速版本SXSSFWorkbook,耗时6秒,速度提升近10倍
System.out.println("写入文件完成,共耗时:"+(end-begin)/1000+" 秒");
}
@Test // 测试 03、07版本 读
public void testRead03() throws Exception{
FileInputStream fis = new FileInputStream(Path+"NBA统计表.xls");
// 读取工作簿
Workbook workbook = new HSSFWorkbook(fis);
//Workbook workbook = new XSSFWorkbook(fis); 07版本就实现类不同而已
// 获取第一个表
Sheet sheet = workbook.getSheetAt(0);
// 获取第一行第一列的单元格
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);
// 输入单元格内容
System.out.println(cell.getStringCellValue());
fis.close();
}
@Test // 在工作中,一个excel中会有很多数据类型,所以在读的时候就要加各种判断类型的逻辑
public void testCellType() throws Exception{
InputStream is = new FileInputStream(Path+"会员消费商品明细表.xls");
Workbook workbook = new HSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
// 读取第一行表头数据
Row row = sheet.getRow(0);
if(row!=null){
int cellCount = row.getPhysicalNumberOfCells();// 获取改行的长度
for (int i = 0; i <cellCount ; i++) {
Cell cell = row.getCell(i);
if(cell!=null){
String cellValue = cell.getStringCellValue();// 因为第一行一般都是表头,所以直接用String类型来接收了
System.out.print(cellValue+"|");
}
}
System.out.println();//换行
}
// 读取商品列表数据
int rowCount = sheet.getPhysicalNumberOfRows();//获取该sheet有几行
for (int i = 1; i <rowCount ; i++) { // 第一行已经获取过了,所以从第二行开始读取数据
Row rowData = sheet.getRow(i);
if(rowData!=null){
int cellCount = rowData.getPhysicalNumberOfCells();//获取该行共有几列
for (int j = 0; j < cellCount; j++) {
Cell cell = rowData.getCell(j);
if(cell!=null){
int cellType = cell.getCellType();//获取单元格数据类型
String cellValue = "";
switch (cellType) {
case HSSFCell.CELL_TYPE_STRING://字符串
System.out.print("【STRING】");
cellValue = cell.getStringCellValue();
break;
case HSSFCell.CELL_TYPE_BOOLEAN://布尔
System.out.print("【BOOLEAN】");
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case HSSFCell.CELL_TYPE_BLANK://空
System.out.print("【BLANK】");
break;
case HSSFCell.CELL_TYPE_NUMERIC:
System.out.print("【NUMERIC】");
//cellValue = String.valueOf(cell.getNumericCellValue());
if (HSSFDateUtil.isCellDateFormatted(cell)) {//日期
System.out.print("【日期】");
Date date = cell.getDateCellValue();
cellValue = new DateTime(date).toString("yyyy-MM-dd");
} else {
// 不是日期格式,则防止当数字过长时以科学计数法显示
System.out.print("【转换成字符串】");
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
cellValue = cell.toString();
}
break;
case Cell.CELL_TYPE_ERROR:
System.out.print("【数据类型错误】");
break;
}
System.out.println(cellValue);
}
}
}
}
is.close();
}
}
三、EasyExcel框架
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。
下面是使用EasyExcel的代码示例:
- 创建一个实体类
@Data
public class BasketballPlayer {
// @ExcelProperty 将实体类中字段和Excel中的标题互相绑定
@ExcelProperty("球员")
private String name;
@ExcelProperty("数字")
private Integer number;
@ExcelProperty("球队")
private String team;
@ExcelProperty("生日")
private Date Birthday;
/**
* ExcelIgnore 可以用来设置所要忽略的字段
*/
@ExcelIgnore
private String ignore;
}
- 创建针对此实体类的一个监听器
// 有个很重要的点 BasketballListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class BasketballListener extends AnalysisEventListener<BasketballPlayer> {
@Override
public void invoke(BasketballPlayer basketballPlayer, AnalysisContext analysisContext) {
System.out.println("====invoke方法执行,每读到一行数据就会走一次这个方法===");
//这里的basketballPlayer就是excel中的数据,已经自动从excel中读取并封装到该实体类中了
System.out.println(basketballPlayer.toString());
System.out.println("这里可以进行一些逻辑处理,或者保存到数据库,等操作都可以进行");
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("====整个sheet读完之后,doAfterAllAnalysed方法执行===");
}
}
- 使用EasyExcel进行读写测试
public class EasyExcelTest {
String path = "D:\\IdeaProjects\\testdemo\\poi_demo\\";
@Test
public void testRead(){
String filePath = path +"NBA球员信息表.xlsx";
EasyExcel.read(filePath, BasketballPlayer.class, new BasketballListener()).sheet().doRead();
}
@Test
public void testWrite(){
String filePath = path +"NBA统计表.xls";
EasyExcel.write(filePath,BasketballPlayer.class).sheet("模板1").doWrite(data());
}
// 模拟要写入excel的数据
private List<BasketballPlayer> data() {
List<BasketballPlayer> list = new ArrayList<BasketballPlayer>();
for (int i = 0; i < 10; i++) {
BasketballPlayer data = new BasketballPlayer();
data.setName("詹姆斯"+i+"号");
data.setNumber(i);
data.setTeam("球队"+i);
data.setBirthday(new Date());
list.add(data);
}
return list;
}
}
从上面的示例中可以看到,使用EasyExcel几乎一行代码就可以完成对Excel的读写操作,而且他帮我们完成了excel数据封装到实体类这一步,且我们只需要在listener中写自己的业务逻辑即可。
不过上面的示例代码只是最简单的EasyExcel的使用,真正到开发中使用的话还需要根据具体的需求来具体应用。
EasyExcel官网上有具体的使用文档:EasyExcel官网地址