2.6-POI读取不同数据类型的数据
表格数据:
这里我们已经将我们平常能够遇到的数据类型全部都包含到了.
接下来我们通过这段代码进行测试:
@Test public void testMultipleTypeRead()throws Exception{ FileInputStream fileInputStream=new FileInputStream(PATH+"test.xls"); Workbook workbook=new HSSFWorkbook(fileInputStream); //获取表格的列名 Sheet sheet=workbook.getSheetAt(0); Row rowTitle=sheet.getRow(0); int cellNum=rowTitle.getLastCellNum(); if(rowTitle!=null){ for(int i=0;i<cellNum;i++){ Cell cell=rowTitle.getCell(i); int cellType=cell.getCellType(); if(cell!=null){ System.out.print(cell+"-"+cellType+" | "); } } } System.out.println(); //获取表格的数据部分 int RowNum=sheet.getLastRowNum(); for(int i=1;i<=RowNum;i++){ Row rowData=sheet.getRow(i); if(rowData!=null){ int cellnum=rowData.getLastCellNum(); for(int j=0;j<cellnum;j++){ Cell cell=rowData.getCell(j); int cellType=cell.getCellType(); if(cell!=null){ //根据单元格数据类型进行相应的数据输出 switch (cellType){ //数字类型数据 case HSSFCell.CELL_TYPE_NUMERIC: System.out.print(cell.getNumericCellValue()+"-"+cellType+" | "); continue; //字符串类型数据 case HSSFCell.CELL_TYPE_STRING: System.out.print(cell.getStringCellValue()+"-"+cellType+" | "); continue; //公式类型 case HSSFCell.CELL_TYPE_FORMULA: System.out.print("null"+"-"+cellType+" | "); continue; //空单元格 case HSSFCell.CELL_TYPE_BLANK: System.out.print(cell.getStringCellValue()+"-"+cellType+" | "); continue; //布尔值类型 case HSSFCell.CELL_TYPE_BOOLEAN: System.out.print(cell.getBooleanCellValue()+"-"+cellType+" | "); continue; //错误单元格 case HSSFCell.CELL_TYPE_ERROR: System.out.print(cell.getErrorCellValue()+"-"+cellType+" | "); continue; } } } System.out.println(); } } }
这里我们可以看到能够输出下面的结果:
其中上面的单元格类型变量,我们既可以通过直接的0,1,2…来定义,同时也能够直接通过HSSFCell的变量值来直接定义.
这里为了方便大家更好的理解,我们点进源码查看一下:
我们进入HSSFCell之后并没有看到我们想要的变量名:
但是我们看到HSSFCell他是实现了Cell这个接口的,所以不出意外,这些变量应该就是在Cell里面定义,所以我们再点进Cell里面看.发现的确就是如我们想的一样:
并且他们的返回值都是int类型的,所以这就行号解释了为什么能够直接调用这些变量了.
2.7-POI计算公式
这里我们在之前的test.xls文件里面为一个单元格增加了一个公式:
接下来我们通过下面的代码将公式以及公式计算的结果读取出来:
@Test public void testFORMULA()throws Exception{ FileInputStream fileInputStream=new FileInputStream(PATH+"test.xls"); Workbook workbook=new HSSFWorkbook(fileInputStream); //获取到包含公式的单元格 Sheet sheet=workbook.getSheetAt(0); Row row=sheet.getRow(3); Cell cell=row.getCell(7); //读取计算的公式 FormulaEvaluator formulaEvaluator=new HSSFFormulaEvaluator((HSSFWorkbook) workbook); int cellType=cell.getCellType(); switch (cellType){ //单元格的类型是公式类型 case HSSFCell.CELL_TYPE_FORMULA: //公式内容 String formula=cell.getCellFormula(); System.out.println(formula); //执行公式之后,单元格内的值 CellValue evaluate=formulaEvaluator.evaluate(cell); String cellValue=evaluate.formatAsString(); System.out.println(cellValue); break; }
接着我们来运行一下,看看结果吧:
可以看到输出的结果和我们在Excel里面看到的结果是一样的.
到这里我们关于POI的操作基本就已经结束了,接下来我们就主要了解一下EsayExcel.
3.EsayExcel:
真的是没有对比就没有伤害,在使用POI的过程中,感觉整个的流程还是比较简单的,毕竟就和我们平常写Excel表格的步骤是一样的,但是在真正使用了EasyExcel之后才发现,POI真的是弱爆了,并且在POI中我们需要使用到大量的for循环,这样会严重影响我们程序的性能,但是EasyExcel就已经帮我们优化好了,使得整个程序的性能一直处于十分强悍的状态.
并且就如同我们上面分析过的一样,POI本质上主要是在内存中进行数据的读写,但是在EasyExcel中就不一样了,他是直接将大部分的工作直接转移到了硬盘上这样就能大大减少我们内存的使用,性能能够得到大幅度的提升.对比如下图所示:
3.1-EasyExcel介绍:
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
EasyExcel的GitHub地址:https://github.com/alibaba/easyexcel
EasyExcel的官方文档:https://www.yuque.com/easyexcel/doc/easyexcel
其实简单总结一下EasyExcel的特点就是一个字:快.
EasyExcel所需的依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.6</version> </dependency>
我们在引入EasyExcel依赖之后,我们需要注意下面的问题,因为EasyExcel里面已经集成了很多的依赖,并且里面就包含了POI的依赖:
所以我们需要将我们之前引入的POI的依赖注释掉,否则会出现依赖的重复.
3.2-EasyExcel数据写入操作
首先我们需要创建一个实体类.用来映射到我们在Excel中将要填充的对象
import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; import java.util.Date; @Data public class DemoData { @ExcelProperty("字符串标题") private String string; @ExcelProperty("日期标题") private Date date; @ExcelProperty("数字标题") private Double doubleData; /** * 忽略这个字段 */ @ExcelIgnore private String ignore; }
并且EasyExcel还为我们提供了一些注解,方便我们的工作
@ExcelProperty(""):用来标注Excel中字段的标题
@ExcelIgnore:用来表示该字段忽略,不用添加到Excel中
public class TestEasyExcel { String PATH="D:/software/IDEA/projects/rang-poi/"; //填充我们即将写入Excel中的数据 private List<DemoData> data() { List<DemoData> list = new ArrayList<DemoData>(); 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. 直接写即可 */ @Test public void simpleWrite() { // 写法1 //创建文件名 String fileName = PATH+ "easyexcel.xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); // // 写法2 // fileName = PATH+ "easyexcel.xlsx"; // // 这里 需要指定写用哪个class去写 // ExcelWriter excelWriter = null; // try { // excelWriter = EasyExcel.write(fileName, DemoData.class).build(); // WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); // excelWriter.write(data(), writeSheet); // } finally { // // 千万别忘记finish 会帮忙关闭流 // if (excelWriter != null) { // excelWriter.finish(); // } // } } }
这样我们的数据写入就完成了,运行代码之后我们就可以看到已经在我们的项目路径下生成了easyexcel文件了
打开之后
数据也的确已经插入进来了
上面的代码中有两段执行数据写入的方法,第一段代码就是直接将数据写入到文件中,第二段代码就类似于POI中的通过for循环将数据一条一条的写入进去,显然第二种方法效率较低,推荐使用第一种.这里对比POI之后,我们可以发现EasyExcel极大的降低了代码量.
3.3-EasyExcel数据读取操作
首先我们需要创建一个监听器:
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 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 */ @Override public void invoke(DemoData data, AnalysisContext context) { System.out.println(JSON.toJSONString(data)); 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()); demoDAO.save(list); LOGGER.info("存储数据库成功!"); } }
之后我们需要根据自己的需要创建一个DAO功能其实就类似于我们的service层,可以在这里面定义我们后来可能加入的与数据库的相关操作的方法
/** * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。 **/ public class DemoDAO { public void save(List<DemoData> list) { // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入 } }
创建完成之后我们的功能基本就可以了,之后就可以进行测试了:
/** * 最简单的读 * <p>1. 创建excel对应的实体对象 参照{@link DemoData} * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} * <p>3. 直接读即可 */ @Test public void simpleRead() { // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 // 写法1: String fileName = PATH+ "easyexcel.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); // // 写法2: // String fileName = PATH+ "easyexcel.xlsx"; // ExcelReader excelReader = null; // try { // excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build(); // ReadSheet readSheet = EasyExcel.readSheet(0).build(); // excelReader.read(readSheet); // } finally { // if (excelReader != null) { // // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 // excelReader.finish(); // } // } }
运行完成之后我们看到这样的结果:
就说明的确是已经将数据读出来了.
这里其实和上面数据写入是一样的,同样也有两个方法.同样的第二个也是类似于for循环,循环遍历数据,所以 效率比较慢,还是建议第一种方法.