常用场景
1、将用户信息导出为excel表格(导出数据…)
2、将Excel表中的信息录入到网站数据库(习题上传…)
开发中经常会设计到excel的处理,如导出Excel,导入Excel到数据库中! 操作Excel目前比较流行的就是 Apache POI 和 阿里巴巴的 easyExcel !
首先execl有两个版本,分别是03版和07版。
通过鼠标右键即可观看(以xls,xlsx结尾)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lkkw0fFK-1610874934517)(C:\Users\王东梁\AppData\Roaming\Typora\typora-user-images\image-20210117141319852.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AtA6xjSz-1610874934522)(C:\Users\王东梁\AppData\Roaming\Typora\typora-user-images\image-20210117141527668.png)]
Poi(适合小数据量)
Apache POI 官网:https://poi.apache.org/
POI是Apache软件基金会的,POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。
所以POI的主要功能是可以用Java操作Microsoft Office的相关文件,这里我们主要讲Excel
小数据写
1 .导入依赖
<dependencies><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>2.10.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.1</version></dependency></dependencies>
2 .开启读写操作,代码走起
无非就是对api的充分认识,接下来我们先去了解他的api
Workbook wordkbook =new HSSFWorkbook();//创建一个Workbook对象wordkbook.createSheet();//创建表名,如果不写参数,会有默认值Row row1=sheet.createRow(0);//根据里面的数字拿到对应的行,0默认为第一行Cell cell = row1.createCell(0);//根据行对象创建单元格,这里0为第一个cell.setCellValue("");//可以给单元格赋值
写入一个Excel
package com.kuang;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.joda.time.DateTime;
import org.junit.Test;import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;public class ExcelWriteTest {//先要有个路劲static String path="D:\JAVA---EasyExcel\TEST";@Testpublic void testWrite03(String[] args) throws IOException {//1,创建一个工作薄Workbook wordkbook =new HSSFWorkbook();//表名Sheet sheet=wordkbook.createSheet("灰灰统计表");//创建行Row row1=sheet.createRow(0);//4.创建一个单元格Cell cell = row1.createCell(0);cell.setCellValue("今日新增观众");Cell cell2 = row1.createCell(1);cell2.setCellValue("卢本伟");//创建行Row row2=sheet.createRow(1);//4.创建一个单元格Cell cell3 = row2.createCell(0);cell3.setCellValue("统计时间");Cell cell24= row2.createCell(1);String time=new DateTime().toString("yyyy-MM-dd HH:mm:ss");cell24.setCellValue(time);//生成一张表 03是xls 07是xlsxFileOutputStream fileOutputStream = new FileOutputStream(path + "灰灰统计表03.xls");wordkbook.write(fileOutputStream);fileOutputStream.close();System.out.println("灰灰统计表03已生成");}@Testpublic void testWrite07() throws IOException {//1,创建一个工作薄Workbook wordkbook =new XSSFWorkbook();//表名Sheet sheet=wordkbook.createSheet("灰灰统计表");//创建行Row row1=sheet.createRow(0);//4.创建一个单元格Cell cell = row1.createCell(0);cell.setCellValue("今日新增观众");Cell cell2 = row1.createCell(1);cell2.setCellValue("卢本伟");//创建行Row row2=sheet.createRow(1);//4.创建一个单元格Cell cell3 = row2.createCell(0);cell3.setCellValue("统计时间");Cell cell24= row2.createCell(1);String time=new DateTime().toString("yyyy-MM-dd HH:mm:ss");cell24.setCellValue(time);//生成一张表 03是xls 07是xlsxFileOutputStream fileOutputStream = new FileOutputStream(path + "\灰灰统计表07.xlsx");wordkbook.write(fileOutputStream);fileOutputStream.close();System.out.println("灰灰统计表07已生成");}}
上面写完后会在项目目录下生成一个表格
03 | 07 版本的写,就是对象不同,方法一样的!
大数据写
HSSF
缺点:最多只能处理65536行,否则会抛出异常
java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0…65535)
优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度快
@Test
public void testWrite03BigData() throws IOException {long begin = System.currentTimeMillis();HSSFWorkbook workbook = new HSSFWorkbook();HSSFSheet sheet = workbook.createSheet();for (int rowNum = 0; rowNum < 65535; 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");workbook.write(outputStream);outputStream.close();long end = System.currentTimeMillis();System.out.println((double) (end-begin)/1000);}
XSSF
缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如100万条
优点:可以写较大的数据量,如20万条
@Test
public void testWrite07BigData() throws IOException {long begin = System.currentTimeMillis();Workbook workbook = new XSSFWorkbook();Sheet sheet = workbook.createSheet();for (int rowNum = 0; rowNum < 655350; 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.xlsx");workbook.write(outputStream);outputStream.close();long end = System.currentTimeMillis();System.out.println((double) (end-begin)/1000);}
SXSSF
优点:可以写非常大的数据量,如100万条甚至更多条,写数据速度快,占用更少的内存
注意:
过程中会产生临时文件,需要清理临时文件
默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件 如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook ( 数量 )
@Test
public void testWrite07BigDataS() throws IOException {long begin = System.currentTimeMillis();Workbook workbook = new SXSSFWorkbook();Sheet sheet = workbook.createSheet();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((double) (end-begin)/1000);}
SXSSFWorkbook-来至官方的解释:实现“BigGridDemo”策略的流式XSSFWorkbook版本。这允许写入 非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。
请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释…仍然只存 储在内存中,因此如果广泛使用,可能需要大量内存。
读取单一类型的数据
这个操作跟上述的写并没有什么不同,不同就是方法是get而不是set
static String path="F:\\demo\\javapoi\\demopoi";@Testpublic void testRead03() throws IOException {//Sheet sheet=workbook.createSheet("统计表");//sheet操作表中元素FileInputStream fileInputStream = new FileInputStream(path + "\灰灰统计表03.xls");Workbook workbook=new HSSFWorkbook(fileInputStream);Sheet sheet = workbook.getSheetAt(0);
// Sheet sheet2 = workbook.getSheet("灰灰统计表");Row row = sheet.getRow(1);Cell cell = row.getCell(0);Cell cell2 = row.getCell(1);System.out.println(cell.getStringCellValue());System.out.println(cell2.getStringCellValue());fileInputStream.close();}
这里值得注意的是,使用表格对象要注意三种创建方式
- POI-HSSF
- POI-XSSF
- SXSSF
**HSSF:*Excel97-2003版本,扩展名为.xls。一个sheet最大行数*65536,最大列数256。
**XSSF:*Excel2007版本开始,扩展名为.xlsx。一个sheet最大行数*1048576,最大列数16384。
SXSSF:**是在XSSF基础上,POI3.8版本开始提供的**支持低内存占用的操作方式,扩展名为.xlsx。
Excel版本兼容性是向下兼容。
读取不同类型的数据
在读取数据的时候我们需要先判断值类型,才能用对应API
下面这个是先拿到表头那一行,相当于数据库的字段
FileInputStream fileInputStream = new FileInputStream(path + "数据表07.xlsx");Workbook workbook=new XSSFWorkbook(fileInputStream);Sheet sheet = workbook.getSheetAt(0);Row rowTitle = sheet.getRow(0);if(rowTitle!=null){int cellCount=rowTitle.getPhysicalNumberOfCells(); //拿到第row行的那一行的总个数for (int i = 0; i <cellCount ; i++) { //循环个数取出Cell cell = rowTitle.getCell(i);if(cell!=null){ //如果不等于空取出值int cellType = cell.getCellType(); //这里是知道我们标题是String,考虑不确定的时候怎么取String cellValue = cell.getStringCellValue();System.out.print(cellValue+"|");}}System.out.println();}
下面接着读取对应的数据,这里就需要我们刚刚讲的类型判断
int cellType=cell.getCellType();利用这个,然后判断它的XSSFCell类型再具体输出
//获取表中内容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++){System.out.print("["+(rowNum+1+"-"+(cellNum+1)+"]"));Cell cell = rowData.getCell(cellNum);//匹配数据类型if(cell!=null){int cellType=cell.getCellType();switch (cellType){case XSSFCell.CELL_TYPE_STRING: System.out.print("字符串:"+cell.getStringCellValue());break;case XSSFCell.CELL_TYPE_BOOLEAN: System.out.print("布尔:"+cell.getBooleanCellValue());break;case XSSFCell.CELL_TYPE_NUMERIC:if(HSSFDateUtil.isCellDateFormatted(cell)){System.out.println("日期格式:"+new DateTime(cell.getDateCellValue()).toString("yyyy-MM-dd HH:mm:ss"));break;}elsecell.setCellType(XSSFCell.CELL_TYPE_STRING);System.out.print("整形:"+cell.toString());break;case XSSFCell.CELL_TYPE_BLANK: System.out.print("空");break;case XSSFCell.CELL_TYPE_ERROR: System.out.print("数据类型错误");break;case Cell.CELL_TYPE_FORMULA://拿到计算公式XSSFFormulaEvaluator FormulaEvaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);String formula=cell.getCellFormula();System.out.println("公式:"+formula);//CellValue evaluate = FormulaEvaluator.evaluate(cell);String cellValue=evaluate.formatAsString();System.out.println(cellValue);break;default:break;}}}}}fileInputStream.close();
EasyExcel(适合大数据量)
这个的出现比poi简单非常多,只需要认清他的对应API就可以进行操作了,即使记不清楚了,我们也可以去网站上在线COPY
https://www.yuque.com/
导入依赖
//注意它里面自带poi依赖,如果重复带入会报ClassNotfound <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.16</version></dependency>
读写操作
我们以上面这个表格为例来进行读写操作,触类旁通
写操作
先来个实体类方便插入数据
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;import java.util.Date;@Data //lombok
public class DemoData {@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;@ExcelProperty("数字标题")private Double doubleData;/*** 忽略这个字段*/@ExcelIgnore //注意这个注解是高版本的easyexcel依赖才有private String ignore;
}
再来一个工具类方便我们写数据
public class utilList {public static 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;}
}
进行写
@Testpublic void simpleWrite() {// 写法1String path="D:\JAVA---EasyExcel\TEST";String fileName = path + "\EasyTest.xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());// 写法2
// fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// // 这里 需要指定写用哪个class去写
// ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
// WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
// excelWriter.write(data(), writeSheet);
// // 千万别忘记finish 会帮忙关闭流
// excelWriter.finish();}
写完就有了这样一个表格
读操作
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.60</version>
</dependency>
首先我们一个监听器,因为和poi的不同,easyExcel是spring接管的,自己监控和改写方法
package com.example.easyExcel;import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.example.Dao.DemoDAO;
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) {LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));System.out.println( JSON.toJSONString(data));list.add(data);// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOMif (list.size() >= BATCH_COUNT) {saveData();// 存储完成清理 listlist.clear();}}/*** 所有数据解析完成了 都会来调用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();LOGGER.info("所有数据解析完成!");}/*** 加上存储数据库*/private void saveData() {LOGGER.info("{}条数据,开始存储数据库!", list.size());demoDAO.save(list);LOGGER.info("存储数据库成功!");}
}
这里的saveData是为了给读取前台的表格之后可以执行这个然后通过下面的方法持久化到数据库,而且这里默认是5条持久一次
/*** 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。**/
public class DemoDAO {public void save(List<DemoData> list) {// 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入}
}
@Override报错
可能是jdk版本不是8
检查java和javac
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wa1x84lP-1610874934528)(C:\Users\王东梁\AppData\Roaming\Typora\typora-user-images\image-20210117165613834.png)]
开始读的操作
/*** 最简单的读* <p>1. 创建excel对应的实体对象 参照{@link DemoData}* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}* <p>3. 直接读即可*/@Testpublic void simpleRead() {String fileName = path + "EasyTest.xlsx";EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();}
总结
- easyExcel的确比poi方便,但是它的读需要编写监听器
- 建议大数据用easyExcel,因为大数据时poi对于内存消耗非常大
- 由于apache poi和jxl,excelPOI都有一个严重的问题,就是非常消耗内存,特别处理数据量多时,速度慢并且时有异常发生,所以改用由阿里研发的easyExcel更可靠一些,它的官方建议对于1000行以内的采用原来poi的写法一次读写,但于1000行以上的数据,有用了一行行进行解析的方案,这样避免了内存的溢出。
- EasyExcel扩展功能很多,且Api式调用真的轻松很多