1.POI
1.1POI简介
在Java中,POI是指Apache POI(Poor Obfuscation Implementation),它是一个开源的Java库,用于处理Microsoft Office文档格式文件,如Excel、Word、PowerPoint等。POI提供了一组API,使得开发者可以通过Java代码读取、写入和修改Office文档,可以方便地操作Excel表格、Word文档、PowerPoint演示文稿等。
POI主要包括以下三个组件:
-
POI-HSSF:用于操作Excel2003及以前版本的.xls格式文件。
-
POI-XSSF:用于操作Excel2007及以后版本的.xlsx格式文件。
-
POI-HWPF:用于操作Word97-2003版本的.doc格式文件。
-
POI-HSLF:提供读写Microso PowerPointr格式档案的功能。
-
HDGF - 提供读写MicrosoftVisio格式档案的功能。
除了这5个主要的组件,POI还提供了其他的组件,例如POI-XWPF用于操作Word2007及以后版本的.docx格式文件、POI-HSLF用于操作PowerPoint文档等等。
使用POI,开发者可以读取Excel表格数据、创建新的Excel表格、编辑已有的Excel表格、读取Word文档内容等等。同时,POI也支持将文档导出为PDF、HTML等格式。
1.2Excel的区别
注意:03和07版本存在兼容性问题,03版本最多只能有65535行
03和07版本的写方法是一样的,但是对象不同。
1.工作簿:
2.工作表:
3.行:
4.列:
1.3POI-Excel写
1.3.1依赖导入
<!--导入依赖--><dependencies><!--xls(03)--><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.2</version></dependency><!--xlsx(07)--><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.2</version></dependency><!--日期格式化工具--><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>2.10.14</version></dependency><!--test--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>
1.3.2 03版本
//03版本@Testpublic void testWrite() {//1.创建一个工作簿Workbook workbook = new HSSFWorkbook();//2.创建一个工作表Sheet sheet = workbook.createSheet("测试表");//3.创建一个行(1,1)Row row1 = sheet.createRow(0);//4.创建一个单元格1Cell cell11 = row1.createCell(0);cell11.setCellValue("表头1");Cell cell12 = row1.createCell(1);cell12.setCellValue("001");//第二行(2,1)Row row2 = sheet.createRow(1);Cell cell21 = row2.createCell(0);cell21.setCellValue("统计时间");Cell cell22 = row2.createCell(1);String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");cell22.setCellValue(time);//生成表(Io流) 03版本 xlsFileOutputStream outputStream = null;try {outputStream = new FileOutputStream(PATH + "测试表.xls");} catch (FileNotFoundException e) {throw new RuntimeException(e);}//输出try {workbook.write(outputStream);} catch (IOException e) {throw new RuntimeException(e);}//关闭流try {outputStream.close();} catch (IOException e) {throw new RuntimeException(e);}System.out.println("文件生成完毕!");}
1.3.3 07版本
//07版本@Testpublic void testWrite07() {//1.创建一个工作簿Workbook workbook = new XSSFWorkbook();//2.创建一个工作表Sheet sheet = workbook.createSheet("测试表");//3.创建一个行(1,1)Row row1 = sheet.createRow(0);//4.创建一个单元格1Cell cell11 = row1.createCell(0);cell11.setCellValue("表头1");Cell cell12 = row1.createCell(1);cell12.setCellValue("001");//第二行(2,1)Row row2 = sheet.createRow(1);Cell cell21 = row2.createCell(0);cell21.setCellValue("统计时间");Cell cell22 = row2.createCell(1);String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");cell22.setCellValue(time);//生成表(Io流) 07版本 xlsxFileOutputStream outputStream = null;try {outputStream = new FileOutputStream(PATH + "测试表.xlsx");} catch (FileNotFoundException e) {throw new RuntimeException(e);}//输出try {workbook.write(outputStream);} catch (IOException e) {throw new RuntimeException(e);}//关闭流try {outputStream.close();} catch (IOException e) {throw new RuntimeException(e);}System.out.println("文件生成完毕!");}
注意对象的区别和文件后缀
1.4 大文件写入
数据批量导入!
大文件写HSSF:
缺点:最多只能处理65536行,否则就会抛出异常
优点:过程中写入缓存,不操作磁盘,最后一次写入磁盘,速度快
大文件写XSSF:
缺点:写数据时速度慢,非常耗内存,也可能会发生内存溢出,如:100万条时
优点:可以写入较大量数据,如:20万条。
大文件写SXSSF
它是一种基于流式处理的用户模型,适用于处理大量数据时,以减少内存消耗并提高性能。
与传统的基于用户模型的 API(如 HSSF)相比,SXSSF 可以将生成的数据直接写入磁盘,而不是先将数据全部加载到内存中,从而节省了内存开销。这使得 SXSSF 更适合处理大规模数据集或需要导出大型 Excel 文件的场景。
优点:可以写非常大的数据量,如100万条甚至更多,写数据速度快,占用更少内存少。
注意:过程中会产生临时文件,需要清理临时文件,默认由100条记录被保存在内存中,如果超过这数量,则前面的文件将会被写入临时文件。如果想自定义内存中数据的数量,可以使用
new SXSSFWorkbook(数量)
1.4 POI-Excel读
1.4.1 基本的读取 03 07 版本
package com.yang;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.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;import java.io.FileInputStream;
import java.io.FileNotFoundException;/*** @author yzh* @date 2024-01-14*/public class ExcelReadTest {public static final String PATH = "E:\\ExcleTest\\";@Testpublic void readTest03() throws Exception {//1.获取文件流FileInputStream fileInputStream = new FileInputStream(PATH + "测试表.xls");//2..创建一个工作簿 使用excel能操作的工作簿都能操作Workbook workbook = new HSSFWorkbook(fileInputStream);//3.获取表Sheet sheet = workbook.getSheetAt(0);//4.获取行Row row = sheet.getRow(0);//5.获取列Cell cell = row.getCell(1);//getStringCellValue获取字符串类型System.out.println(cell.getStringCellValue());fileInputStream.close();}@Testpublic void readTest07() throws Exception {//1.获取文件流FileInputStream fileInputStream = new FileInputStream(PATH + "测试表.xlsx");//2..创建一个工作簿 使用excel能操作的工作簿都能操作Workbook workbook = new XSSFWorkbook(fileInputStream);//3.获取表Sheet sheet = workbook.getSheetAt(0);//4.获取行Row row = sheet.getRow(0);//5.获取列Cell cell = row.getCell(1);//getStringCellValue获取字符串类型System.out.println(cell.getStringCellValue());fileInputStream.close();}
}
**注意获取值的类型**
1.4.2 读取不同的数据类型的值
@Testpublic void cellTypeTest() throws Exception {//1.获取文件流FileInputStream fileInputStream = new FileInputStream(PATH + "权益导入模板.xlsx");//2..创建一个工作簿 使用excel能操作的工作簿都能操作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) {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) {//如果行不为空//读取列int cellCount = rowTitle.getPhysicalNumberOfCells();for (int cellNum = 0; cellNum < cellCount; cellNum++) {Cell cell = rowData.getCell(cellNum);//匹配列的数据类型if (cell != null) {CellType cellType = cell.getCellType();String cellValue = "";switch (cellType) {case STRING:System.out.println("STRING");cellValue = cell.getStringCellValue();break;case NUMERIC:System.out.println("NUMERIC");if (DateUtil.isCellDateFormatted(cell)) {System.out.println("日期");Date date = cell.getDateCellValue();SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");cellValue = dateFormat.format(date);} else {System.out.println("普通数字");//防止数字过长cell.setCellType(CellType.STRING);//转换为字符串输出cellValue = cell.getStringCellValue();}break;case BOOLEAN:System.out.println("BOOLEAN");cellValue = String.valueOf(cell.getBooleanCellValue());break;case _NONE:System.out.println("_NONE");break;case ERROR:System.out.println("ERROR");break;}System.out.println(cellValue);}}}}fileInputStream.close();}//优化后@Testpublic void testReadExcel() throws Exception {String filePath = PATH + "权益导入模板.xlsx";try (InputStream is = new FileInputStream(filePath);Workbook workbook = new XSSFWorkbook(is)) {Sheet sheet = workbook.getSheetAt(0);// 创建自定义的DataFormatter对象DataFormatter dataFormatter = new DataFormatter(Locale.ENGLISH);// 读取标题行Row titleRow = sheet.getRow(0);List<String> titleList = readRowValues(titleRow, new DataFormatter());// 读取数据行List<List<String>> dataList = new ArrayList<>();for (int i = 1; i <= sheet.getLastRowNum(); i++) {Row dataRow = sheet.getRow(i);if (dataRow == null) {continue;}List<String> rowData = readRowValues(dataRow, new DataFormatter());dataList.add(rowData);}// 输出结果System.out.println("标题行:" + titleList);System.out.println("数据行:" + dataList);}}/*** 读取一行单元格的值** @param row 行对象* @param dataFormatter 数据格式化对象* @return 单元格值列表*/private List<String> readRowValues(Row row, DataFormatter dataFormatter) {List<String> values = new ArrayList<>();for (int i = 0; i < row.getLastCellNum(); i++) {Cell cell = row.getCell(i);String value = dataFormatter.formatCellValue(cell);// 判断是否为日期格式if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {Date date = cell.getDateCellValue();SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");value = dateFormat.format(date);}values.add(value);}return values;}
1.4.3 计算公式(了解即可)
@Testpublic void testFormula() throws Exception {FileInputStream fileInputStream = new FileInputStream(PATH + "求和.xlsx");Workbook workbook = new XSSFWorkbook(fileInputStream);Sheet sheet = workbook.getSheetAt(0);Row row = sheet.getRow(5);Cell cell = row.getCell(0);//拿到计算公式FormulaEvaluator evaluator = new XSSFFormulaEvaluator((XSSFWorkbook)workbook);//输出单元格的内容CellType cellType = cell.getCellType();switch (cellType){case FORMULA:String formula = cell.getCellFormula();System.out.println(formula);//计算CellValue evaluate = evaluator.evaluate(cell);String result = evaluate.formatAsString();System.out.println(result);break;}}
2.EasyExcel
2.1EasyExcel简介
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
2.2使用写
2.2.1引入依赖
<!--EasyExcel--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.0-beta2</version></dependency><!-- FastJSON --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency>
2.2.2创建实体类
package com.yang.data;import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;import java.util.Date;/*** @author yzh* @date 2024-01-14*/
@Data
public class DemoData {@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;@ExcelProperty("数字标题")private Double doubleData;/*** 忽略这个字段*/@ExcelIgnoreprivate String ignore;}
2.2.3测试
package com.yang;import com.alibaba.excel.EasyExcel;
import com.yang.data.DemoData;
import org.apache.commons.collections4.ListUtils;
import org.junit.Test;import java.util.ArrayList;
import java.util.Date;
import java.util.List;/*** @author yzh* @date 2024-01-14*/public class EasyExcelTest {public static final String PATH = "E:\\ExcleTest\\";private List<DemoData> data() {ArrayList<DemoData> list = new ArrayList<>();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;}/*** 最简单的写* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 直接写即可*/@Testpublic void simpleWrite() {String fileName= PATH+"simpleWrite.xlsx";EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());}}
2.3 使用读
2.3.1创建DemoDataListener:【监听器】
package com.yang;import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.yang.data.DemoData;
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 = 5;List<DemoData> list = new ArrayList<DemoData>();/*** 假设这个是一个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*/@Overridepublic void invoke(DemoData data, AnalysisContext context) {System.out.println(JSON.toJSONString(data));list.add(data);System.out.println(list);// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOMif (list.size() >= BATCH_COUNT) {saveData();// 存储完成清理 listlist.clear();}}/*** 所有数据解析完成了 都会来调用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();LOGGER.info("所有数据解析完成!");}/*** 加上存储数据库*/private void saveData() {demoDAO.save(list);}
}
2.3.2持久层DemoDAO:
package com.yang;/*** @author yzh* @date 2024-01-14*/import com.yang.data.DemoData;import java.util.List;/*** 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。**/
public class DemoDAO {public void save(List<DemoData> list) {//持久化操作// 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入}
}
2.3.3测试
package com.yang;import com.alibaba.excel.EasyExcel;
import com.yang.data.DemoData;
import org.apache.commons.collections4.ListUtils;
import org.junit.Test;import java.util.ArrayList;
import java.util.Date;
import java.util.List;/*** @author yzh* @date 2024-01-14*/public class EasyExcelTest {public static final String PATH = "E:\\ExcleTest\\";// 最简单的读@Testpublic void simpleRead() {String fileName = PATH+"simpleWrite.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();}
}
参考文档:关于Easyexcel | Easy Excel
参考视频:【狂神说Java】POI及EasyExcel一小时搞定通俗易懂_哔哩哔哩_bilibili