Apache POI & EasyExcel
常用信息
- 将用户信息导出为excel表格(导出数据)
- 将Excel表中的信息录入到网站数据库(习题上传)
开发中经常会设计到Excel的处理,如导出Excel、导入Excel到数据库中
操作Excel目前比较流行的就是 Apache POI 和阿里巴巴的 easyExcel
Apache POI
Apache POI 官网: http://poi.apache.org/


easyExcel
easyExcel 官网: https://github.com/alibaba/easyexcel

EasyExcel 是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称
EasyExcel 能大大减少占用内存的主要原因是因为在解析 Excel 时没有把文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析
内存问题:
- POI :100w 先加载到内存OOM,在写入文件
- EasyExcel:小部分消化,一行一行的返回
下图是 EasyExcel 和 POI 在解析Excel时的对比图

官方网址: https://www.yuque.com/easyexcel/doc/easyexcel
POI 操作
POI-Excel写
创建项目
- 建立一个空项目,创建普通的Maven的 Model
引入pom依赖
<!-- xls (03) --><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><!-- xlsx (07) --><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.2</version></dependency><!-- 日期格式化工具 --><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>2.9.9</version></dependency><!-- test --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency>
工作簿

- 表格中的对象
- 工作簿
- 工作表
- 行
- 列
- 03版本写文件
public static void testWrite03() {// 1.创建一个工作簿Workbook workbook = new HSSFWorkbook();// 2.创建一个工作表Sheet sheet = workbook.createSheet("ic 测试表 03");// 3.创建一个行Row row0 = sheet.createRow(0);// 4.创建一个列Cell cell11 = row0.createCell(0);// 向格子里面写入数据cell11.setCellValue("第一行第一列");Cell cell12 = row0.createCell(1);cell12.setCellValue("第一行第二列");Row row1 = sheet.createRow(1);Cell cell21 = row1.createCell(0);cell21.setCellValue("统计时间");Cell cell22 = row1.createCell(1);String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");cell22.setCellValue(time);// 生成一张表 (IO流)try {FileOutputStream fileOutputStream = new FileOutputStream(PATH + "\\ic-test03.xls");workbook.write(fileOutputStream);} catch (Exception e) {e.printStackTrace();} finally {System.out.println("success03");}}
07版本写文件
public static void testWrite07() {// 1.创建一个工作簿Workbook workbook = new XSSFWorkbook();// 2.创建一个工作表Sheet sheet = workbook.createSheet("ic 测试表 07");// 3.创建一个行Row row0 = sheet.createRow(0);// 4.创建一个列Cell cell11 = row0.createCell(0);// 向格子里面写入数据cell11.setCellValue("第一行第一列");Cell cell12 = row0.createCell(1);cell12.setCellValue("第一行第二列");Row row1 = sheet.createRow(1);Cell cell21 = row1.createCell(0);cell21.setCellValue("统计时间");Cell cell22 = row1.createCell(1);String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");cell22.setCellValue(time);// 生成一张表 (IO流)try {FileOutputStream fileOutputStream = new FileOutputStream(PATH + "\\ic-test07.xls");workbook.write(fileOutputStream);} catch (Exception e) {e.printStackTrace();} finally {System.out.println("success07");}}
测试类
- ```java package cn.icanci;
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.joda.time.DateTime;
import java.io.FileOutputStream;
public class ExcelWriteTest { private static String PATH = “E:\IdeaHome\maven\apache-poi”;
public static void main(String[] args) {testWrite03();testWrite07();}
}
<a name="45f1d14b"></a>##### 数据批量导入> 大文件写HSSF缺点:最多只能处理65536行,否则会抛出异常```javaException in thread "main" java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)
优点:存储过程中写入缓存,不操作磁盘,最后一次写入磁盘,速度快
public static void testWrite03BigData() throws Exception {// 时间long begin = System.currentTimeMillis();// 创建一个工作簿Workbook workbook = new HSSFWorkbook();// 创建一张表Sheet sheet = workbook.createSheet("03 版本大数据写");// 写入数据for (int rowNum = 0; rowNum < 65536; rowNum++) {Row row = sheet.createRow(rowNum);for (int cellNum = 0; cellNum < 10; cellNum++) {Cell cell = row.createCell(cellNum);cell.setCellValue(cellNum);}}System.out.println("over");FileOutputStream outputStream = new FileOutputStream(PATH + "\\testWrite03BigData.xls");workbook.write(outputStream);outputStream.close();long end = System.currentTimeMillis();System.out.println(end - begin + ":ms");}
大文件写XSSF
缺点:写数据时候特别慢,非常消耗内存,也会发生内存溢出,如100w条数据
优点:可以写较大的数据量,如20w条
public static void testWrite07BigData() throws Exception {// 时间long begin = System.currentTimeMillis();// 创建一个工作簿Workbook workbook = new XSSFWorkbook();// 创建一张表Sheet sheet = workbook.createSheet("07 版本大数据写");// 写入数据for (int rowNum = 0; rowNum < 100000; rowNum++) {Row row = sheet.createRow(rowNum);for (int cellNum = 0; cellNum < 10; cellNum++) {Cell cell = row.createCell(cellNum);cell.setCellValue(cellNum);}}System.out.println("over");FileOutputStream outputStream = new FileOutputStream(PATH + "\\testWrite07BigData.xlsx");workbook.write(outputStream);outputStream.close();long end = System.currentTimeMillis();System.out.println(end - begin + ":ms");}
大文件写SXSSF
优点:可以写非常大的数据量,如100w条数据甚至更多,写数据速度快,占用更少的内存
注意:
写的过程中会出现临时文件,所以需要清理临时文件
默认有100条记录包保存在内存中,如果超过这个数量,则最前面的数据被写入临时文件
如果想自定义内存中数据的数量,可以使用 new SXSSFWorkbook(数量)
public static void testWrite07BigDataSXSSF() throws Exception {// 时间long begin = System.currentTimeMillis();// 创建一个工作簿Workbook workbook = new SXSSFWorkbook();// 创建一张表Sheet sheet = workbook.createSheet("07 版本大数据写");// 写入数据for (int rowNum = 0; rowNum < 100000; rowNum++) {Row row = sheet.createRow(rowNum);for (int cellNum = 0; cellNum < 10; cellNum++) {Cell cell = row.createCell(cellNum);cell.setCellValue(cellNum);}}System.out.println("over");FileOutputStream outputStream = new FileOutputStream(PATH + "\\testWrite07BigDataS.xlsx");workbook.write(outputStream);outputStream.close();// 清除临时文件((SXSSFWorkbook) workbook).dispose();long end = System.currentTimeMillis();System.out.println(end - begin + ":ms");}
SXSSFWorkbook - 官方的解释:实现“BigGridDemo”策略的流式XSSFWorkbook版本,这允许写入非常大的文件而不会消耗内存,因为任何时候都只有可配置的行被保存到内存中
请注意,仍然可能消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释…仍然值存储在内存中,因此广泛使用,仍然可能需要大量内存
POI-Excel 读
03|07
03版本
public static void testRead03()throws Exception {// 获取流FileInputStream inputStream = new FileInputStream(PATH + "ic-test03.xls");// 1.创建一个工作簿 使用Excel可以操作的,都可以操作Workbook workbook = new HSSFWorkbook(inputStream);Sheet sheet = workbook.getSheetAt(0);Row row = sheet.getRow(0);Cell cell = row.getCell(0);// 读取值的时候需要注意类型,否则会报错// 目前获取的是字符串类型 cell.getStringCellValue()System.out.println(cell.getStringCellValue());inputStream.close();}
07版本
public static void testRead07()throws Exception {// 获取流FileInputStream inputStream = new FileInputStream(PATH + "ic-test07.xlsx");// 1.创建一个工作簿 使用Excel可以操作的,都可以操作Workbook workbook = new XSSFWorkbook(inputStream);Sheet sheet = workbook.getSheetAt(0);Row row = sheet.getRow(0);Cell cell = row.getCell(0);// 读取值的时候需要注意类型,否则会报错// 目前获取的是字符串类型 cell.getStringCellValue()System.out.println(cell.getStringCellValue());inputStream.close();}
读取不同的数据类型(最麻烦的地方)
public static void testCellType() 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 cellCount = rowTitle.getPhysicalNumberOfCells();for (int cellNum = 0; cellNum < cellCount; cellNum++) {Cell cell = rowTitle.getCell(cellNum);if (cell != null) {int cellType = cell.getCellType();System.out.print(cell.getStringCellValue() + " ");}}}// 获取表中的内容int rowCount = sheet.getPhysicalNumberOfRows();for (int rowNum = 0; rowNum < rowCount; rowNum++) {Row rowData = sheet.getRow(rowNum);if (rowData != null) {// 读取列int cellCount = rowTitle.getPhysicalNumberOfCells();for (int cellNum = 0; cellNum < cellCount; cellNum++) {System.out.print("[" + (rowNum + 1) + "-" + (cellNum + 1) + "]");// 获得列Cell cell = rowData.getCell(cellNum);// 因为不知道列的类型 所以需要匹配列的类型if (cell != null) {int cellType = cell.getCellType();String cellValue = "";switch (cellType) {//为空case -1:System.out.print("[ _NONE ]");cellValue = "null";break;// 为 空格case 3:System.out.print("[ BLANK ]");break;// 为String类型case 1:System.out.println("[ STRING ]");cellValue = cell.getStringCellValue();break;// 为 number 类型case 0:// 是一个日期if (HSSFDateUtil.isCellDateFormatted(cell)) {System.out.println("[ Date ]");Date dateCellValue = cell.getDateCellValue();cellValue = new DateTime(dateCellValue).toString("yyyy-MM-dd HH:mm:ss");} else {// 是一个数字System.out.println("[ 转换为字符串显示 ]");cell.setCellType(CellType.STRING);cellValue = cell.toString();}break;// 布尔值case 4:System.out.println("[ 布尔值 ]");cellValue = String.valueOf(cell.getBooleanCellValue());break;// 错误case 5:System.out.println("[ 数据类型错误 ]");break;}System.out.println(cellValue);}}}}in.close();}
计算公式(了解即可)
public static void testFormula() throws Exception {FileInputStream in = new FileInputStream(PATH + "计算.xlsx");Workbook workbook = new XSSFWorkbook(in);Sheet sheet = workbook.getSheetAt(0);Row row = sheet.getRow(4);Cell cell = row.getCell(0);// 拿到计算公式FormulaEvaluator formulaEvaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);// 输出单元格的内容int cellType = cell.getCellType();switch (cellType) {// 是公式case 2:String cellFormula = cell.getCellFormula();System.out.println(cellFormula);CellValue evaluate = formulaEvaluator.evaluate(cell);String result = evaluate.formatAsString();System.out.println(result);break;}in.close();}
EasyExcel 操作
官方文档: https://www.yuque.com/easyexcel/doc
Github案例: https://github.com/alibaba/easyexcel
导入依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version></dependency>
写入测试
DemoData.java
@Datapublic class DemoData {@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;@ExcelProperty("数字标题")private Double doubleData;/*** 忽略这个字段*/@ExcelIgnoreprivate String ignore;}
测试写入数据
package cn.icanci;import com.alibaba.excel.EasyExcel;import com.alibaba.excel.ExcelWriter;import com.alibaba.excel.write.metadata.WriteSheet;import java.util.ArrayList;import java.util.Date;import java.util.List;/*** @Author: icanci* @ProjectName: apache-poi* @PackageName: cn.icanci* @Date: Created in 2020/7/17 8:42* @ClassAction:*/public class TestDemo {private static String PATH = "E:\\IdeaHome\\maven\\apache-poi\\";public static void main(String[] args) {new TestDemo().simpleWrite();}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;}/*** 根据 list 写入 excel*/public void simpleWrite() {// 写法1String fileName =PATH+ "simpleWrite1.xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可// write (fileName,格式类)// sheet (表名)// doWrite(数据) 真实情况应该是从数据库读取的数据,或者是用户上传的数据EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());// 写法2fileName = PATH + "simpleWrite2.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();}}}}
最终的结果
读取测试
官方文档: https://www.yuque.com/easyexcel/doc
Github案例: https://github.com/alibaba/easyexcel
固定套路
- 写入,固定类格式进行写入
- 读取,根据监听器设置的规则进行读取
总结
面对对象的方法、思想,学会面向接口编程
