POI
Apache POI是基于Office Open XML标准(OOXML)和Microsoft的OLE 2复合文档格式(OLE2)处理各种文件格式的开源Java项目。 简而言之,我们可以在自己的Java程序中使用POI读写MS Excel、MS Word和MS PowerPoint文件
POI常见的使用场景
- 数据导出,如将系统中的数据导出为Excel表格
- 数据导入,如将Excel表中的信息录入到系统中(数据库)
POI结构及功能
- HSSF:提供读写MS Excel文件的功能
- XSSF:提供读写MS Excel OOXML文件的功能
- HWPF:提供读写MS Excel Word文件的功能
- HSLF:提供读写MS PowerPoint文件的功能
- HDGF:提供读写MS Visio文件的功能
POI的使用
首先需要在项目中导入相关依赖
<!--office03(xls后缀)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<!--office07(xlsx后缀)-->
<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>
Excel文件的写入
@Test
void poiExcelWrite03() throws IOException {
//1、创建一个工作簿 07就是XSSFWorkbook
Workbook workbook = new HSSFWorkbook();
//2、创建一个工作表
Sheet sheet = workbook.createSheet("test_sheet_01");
//3、创建行和单元格,这里的0,0相当于Excel中的A,1
Row row1 = sheet.createRow(0);
Cell cell = row1.createCell(0);
//4、在单元格中写入数据
cell.setCellValue("value01");
//5、创建表文件并写入 07就是.xlsx后缀
FileOutputStream fileOutputStream = new FileOutputStream("E:/java_project/waston_2021/test_poi/excel/" + "test_03.xls");
workbook.write(fileOutputStream);
fileOutputStream.close();
}
在用XSSFWorkbook类对象向office07版的Excel文件写入大量数据时,会耗费较长时间,可以用SXSSFWorkbook类来解决这一问题,其原理是流式缓存
@Test
void poiExcelWriteBigData07() throws IOException {
//计时
long begin = System.currentTimeMillis();
Workbook workbook = new SXSSFWorkbook();
Sheet sheet = workbook.createSheet();
//03 excel最大行数65536 07 则没有上限
for(int rowNum = 0; rowNum < 65536; rowNum++){
Row row = sheet.createRow(rowNum);
char cellChar = 'A';
for(int cellNum = 0; cellNum < 10; cellNum++){
Cell cell = row.createCell(cellNum);
cell.setCellValue(rowNum + "-" + cellChar++);
}
}
FileOutputStream fileOutputStream = new FileOutputStream("E:/java_project/waston_2021/test_poi/excel/" + "bigData_07.xlsx");
workbook.write(fileOutputStream);
fileOutputStream.close();
//使用SXSSFWorkbook写入会生成一个临时文件 这里清除它
((SXSSFWorkbook) workbook).dispose();
long end = System.currentTimeMillis();
System.out.println("用时" + (double)(end - begin) / 1000 + "s");
}
Excel文件的读取
@Test
void poiExcelRead07() throws IOException {
// 文件输入流
FileInputStream fileInputStream = new FileInputStream("E:/java_project/waston_2021/test_poi/excel/" + "people.xlsx");
// 读取Excel文件中的单元格
Workbook workbook = new XSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);
//以String方式获取单元格中的值 若表中为字符 这里以getNumericCellValue获取会报错
System.out.println(cell.getStringCellValue());
fileInputStream.close();
}
对于表中数据有多种类型的解决方法
如表:
@Test
void poiComplexExcelRead07() throws IOException {
FileInputStream fileInputStream = new FileInputStream("E:/java_project/waston_2021/test_poi/excel/" + "people.xlsx");
Workbook workbook = new XSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
//表的标题
Row rowTitle = sheet.getRow(0);
if(rowTitle != null){
//获取标题行的单元格数量 然后遍历
int cellCount = rowTitle.getPhysicalNumberOfCells();
for(int cellNum = 0; cellNum < cellCount; cellNum++){
Cell cell = rowTitle.getCell(cellNum);
if(cell != null){
int cellType = cell.getCellType();
String cellValue = cell.getStringCellValue();
System.out.print(cellValue + "|" );
}
}
System.out.println();
//获取表的数据行数并遍历
int rowCount = sheet.getPhysicalNumberOfRows();
for(int rowNum = 1; rowNum < rowCount; rowNum++){
Row rowData = sheet.getRow(rowNum);
if(rowData != null){
for(int cellNum = 0; cellNum < cellCount; cellNum++){
Cell cell = rowData.getCell(cellNum);
if(cell != null){
int cellType = cell.getCellType();
String cellValue = "";
//匹配数据的类型
switch (cellType){
case XSSFCell.CELL_TYPE_STRING: //字符串
System.out.print("[String]");
cellValue = cell.getStringCellValue();
break;
case XSSFCell.CELL_TYPE_BOOLEAN: //布尔
System.out.print("[Boolean]");
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case XSSFCell.CELL_TYPE_BLANK: //空
System.out.print("[Blank]");
break;
case XSSFCell.CELL_TYPE_NUMERIC: //数字(普通/日期)
System.out.print("[Numeric ");
//判断是否为时间
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(XSSFCell.CELL_TYPE_STRING);
cellValue = cell.toString();
}
break;
case XSSFCell.CELL_TYPE_ERROR: //错误
System.out.print("[Error]");
break;
}
System.out.print(cellValue);
}
}
System.out.println();
}
}
}
fileInputStream.close();
}
对于表中公式的处理,如求和公式:=SUM(B2:B4)
@Test
void poiExcelReadFormula07() throws IOException {
FileInputStream inputStream = new FileInputStream("E:/java_project/waston_2021/test_poi/excel/" + "formula.xlsx");
Workbook workbook = new XSSFWorkbook(inputStream);
Sheet sheet = workbook.getSheetAt(0);
Row row = sheet.getRow(4);
Cell cell = row.getCell(1);
//创建公式处理类对象
FormulaEvaluator formulaEvaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);
//获取单元格内容
int cellType = cell.getCellType();
if(cellType == Cell.CELL_TYPE_FORMULA){
String formula = cell.getCellFormula();
//打印公式
System.out.println(formula);
//通过公式进行计算
CellValue evaluate = formulaEvaluator.evaluate(cell);
String cellValue = evaluate.formatAsString();
System.out.println(cellValue);
}
}
EasyExcel
EasyExcel是一个基于Java的使用简单、省内存的读写Excel的开源项目。它的特点是在尽可能节约内存的情况下支持读写MS Excel (不会因Excel文件过大导致内存溢出错误)
官方网站:https://www.yuque.com/easyexcel/doc/easyexcel
使用
首先导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.0-beta2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
用一个Java类来映射Excel文件的一个sheet
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ToString
public class People {
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年龄")
private int age;
@ExcelProperty("生日")
private Date birthday;
@ExcelProperty("电话")
private String phoneNum;
@ExcelProperty("编号")
private String no;
@ExcelProperty("成绩")
private double score;
@ExcelProperty("是否通过")
private Boolean isPass;
}
写入文件
import com.alibaba.excel.EasyExcel;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@SpringBootTest
class TestEasyExcelApplicationTests {
String Path = "E:/java_project/waston_2021/test_poi/excel/";
private List<People> getPeopleData(){
List<People> people = new ArrayList<>();
for(int i = 0; i < 10; i++){
People p = new People();
p.setName("李四");
p.setAge(25);
p.setBirthday(new Date());
p.setNo("AB023");
p.setPhoneNum("1124568956");
p.setScore(189.6);
p.setIsPass(false);
people.add(p);
}
return people;
}
@Test
//写入文件
void easyExcelWrite() {
String fileName = Path + "EasyExcelWrite.xlsx";
//写入文件
EasyExcel.write(fileName, People.class).sheet("测试").doWrite(getPeopleData());
}
}
读取文件,需要先创建一个监听类
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 PeopleListener extends AnalysisEventListener<People> {
private static final Logger LOGGER = LoggerFactory.getLogger(PeopleListener.class);
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<People> list = new ArrayList<People>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private PeopleDao peopleDao;
public PeopleListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
peopleDao = new PeopleDao();
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param peopleDao
*/
public PeopleListener(PeopleDao peopleDao) {
this.peopleDao = peopleDao;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(People data, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
LOGGER.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
peopleDao.save(list);
LOGGER.info("存储数据库成功!");
}
}
测试读文件
@Test
//读取文件
void easyExcelRead(){
String fileName = Path + "people.xlsx";
EasyExcel.read(fileName, People.class, new PeopleListener()).sheet().doRead();
}