个人理解:
1、03版写入快,但是有数量限制,最多65536条数据(1秒完成)
2、07版写入慢,没有数量限制(65536条需要6秒)
3、07升级版写入快,没有数量限制,会生成临时文件,需要代码删除(65536条1秒完成)
4、03,07版本读Excel,只要注意cell的值类型即可,代码已贴在下面,每个项目都能复用
5、EasyExcel,在尽可能节约内存的情况下支持读写百M的Excel,且只需要一行代码(数据量大的时候推荐使用)
一:Excel大量数据写入(03,07版)
1、导入依赖,03(xls)版,07(xlsx)版共用pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>excel-poi</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--xls(03版本,最多65536行数据)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.11</version>
</dependency>
<!--xls(07版本,没有限制行数)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>4.12</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、03版写入65536行数据大概是一秒钟,超过65536会报错
String PATH = "C:\\GitEE\\excel-poi\\";
/**
* 测试03版的 大数据量的时候
*/
@Test
public void testWrite03BigData() throws Exception {
// 开始时间
long start = System.currentTimeMillis();
// 创建一个工作簿
Workbook workbook = new HSSFWorkbook();
// 创建一个sheet
Sheet sheet = workbook.createSheet();
// 循环65535
for (int rowNumber = 0; rowNumber <65536 ; rowNumber++) {
Row row = sheet.createRow(rowNumber);
for (int cellNumber = 0; cellNumber <10 ; cellNumber++) {
Cell cell = row.createCell(cellNumber);
cell.setCellValue(cellNumber);
}
}
System.out.println("结束");
// 生成一张表(IO流) 03版本就是使用xls结尾!
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "03版本Excel大数据量.xls");
// 输出
workbook.write(fileOutputStream);
// 关闭流
fileOutputStream.close();
long end = System.currentTimeMillis();
// 时间差
System.out.println((end-start)/1000);
}
3、07版写入65536行数据大概是6秒,可以超过65535条数据
String PATH = "C:\\GitEE\\excel-poi\\";
/**
* 测试07版的 大数据量的时候 耗时比较久
*/
@Test
public void testWrite07BigData() throws Exception {
// 开始时间
long start = System.currentTimeMillis();
// 创建一个工作簿
Workbook workbook = new XSSFWorkbook();
// 创建一个sheet
Sheet sheet = workbook.createSheet();
// 循环65535
for (int rowNumber = 0; rowNumber <65535 ; rowNumber++) {
Row row = sheet.createRow(rowNumber);
for (int cellNumber = 0; cellNumber <10 ; cellNumber++) {
Cell cell = row.createCell(cellNumber);
cell.setCellValue(cellNumber);
}
}
System.out.println("结束");
// 生成一张表(IO流) 07版本就是使用xlsx结尾!
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "07版本Excel大数据量.xlsx");
// 输出
workbook.write(fileOutputStream);
// 关闭流
fileOutputStream.close();
long end = System.currentTimeMillis();
// 时间差
System.out.println((end-start)/1000);
}
4、虽然07版可以写入更多数据,但是他比较慢,我们这边看下07升级版,升级版写入十万条数据大概也就是一秒钟,但是他会生成临时文件,需要我们在代码中手动删除
String PATH = "C:\\GitEE\\excel-poi\\";
/**
* 测试 07升级版的 大数据量 会生成临时文件
*/
@Test
public void testWrite07BigDataSuper() throws Exception {
// 开始时间
long start = System.currentTimeMillis();
// 创建一个工作簿
Workbook workbook = new SXSSFWorkbook();
// 创建一个sheet
Sheet sheet = workbook.createSheet();
// 循环65535
for (int rowNumber = 0; rowNumber <100000 ; rowNumber++) {
Row row = sheet.createRow(rowNumber);
for (int cellNumber = 0; cellNumber <10 ; cellNumber++) {
Cell cell = row.createCell(cellNumber);
cell.setCellValue(cellNumber);
}
}
System.out.println("结束");
// 生成一张表(IO流) 07版本就是使用xlsx结尾!
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "07版本Excel大数据量SUPER.xlsx");
// 输出
workbook.write(fileOutputStream);
// 清除临时文件
((SXSSFWorkbook)workbook).dispose();
// 关闭流
fileOutputStream.close();
long end = System.currentTimeMillis();
// 时间差
System.out.println((end-start)/1000);
}
二:读取Excel(这边代码可以保存,以后项目都能使用)
03,07差不多,我们用07读一下,主要注意cell值的类型
String PATH = "C:\\GitEE\\excel-poi\\";
@Test
public void testCellType() throws Exception {
// 获取文件流
FileInputStream fileInputStream = new FileInputStream(PATH + "学生成绩明细表.xlsx");
// 实际用Excel能操作的 这边都能操作
Workbook workbook = new XSSFWorkbook(fileInputStream);
// 通过下标获取sheet
Sheet sheet = workbook.getSheetAt(0);
// 获取标题
Row rowTitle = sheet.getRow(0);
if (rowTitle != null) {
// 重点
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int rowNumber = 0; rowNumber < cellCount; rowNumber++) {
Cell cell = rowTitle.getCell(rowNumber);
if (cell != null) {
int cellType = cell.getCellType();
String cellValue = cell.getStringCellValue();
System.out.print(cellValue + "|");
}
}
System.out.println();
}
// 获取表中的内容
int rowCount = sheet.getPhysicalNumberOfRows();
// 我们从第一行开始读 第0行是标题
for (int rowNumber = 1; rowNumber < rowCount; rowNumber++) {
Row rowData = sheet.getRow(rowNumber);
if (rowData != null) {
// 读取列
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNumber = 0; cellNumber < cellCount; cellNumber++) {
System.out.println("[" + (rowNumber + 1) + "-" + (cellNumber + 1) + "]");
Cell cell = rowData.getCell(cellNumber);
// 匹配数据类型
if (cell != null) {
int cellType = cell.getCellType();
String cellValue = "";
switch (cellType) {
case Cell.CELL_TYPE_STRING: // 字符串
System.out.println("【STRING】");
cellValue = cell.getStringCellValue();
break;
case Cell.CELL_TYPE_BOOLEAN: // 布尔
System.out.println("【BOOLEAN】");
cellValue = String.valueOf(cell.getStringCellValue());
break;
case Cell.CELL_TYPE_BLANK: // 空
System.out.println("【BLANK】");
break;
case Cell.CELL_TYPE_NUMERIC: // 数字(日期,普通数字)
System.out.println("【NUMBER】");
if (HSSFDateUtil.isCellDateFormatted(cell)) {
// 日期
System.out.println("【日期】");
Date date = cell.getDateCellValue();
cellValue = new DateTime(date).toString("YYYY-MM-dd");
} else {
System.out.println("【转换为字符串输出】");
// 不是日期格式,防止数字过长
cell.setCellType(Cell.CELL_TYPE_STRING);
cellValue = cell.toString();
}
break;
case Cell.CELL_TYPE_ERROR:
System.out.println("【数据类型错误】");
break;
}
System.out.println(cellValue);
}
}
}
}
fileInputStream.close();
}
三:EasyExcel 阿里开源项目
快速,简单避免OOM的java处理Excel工具
第一步:导入依赖
easyexcel包中自带了poi,所以我把03,07版本依赖注释掉了
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>excel-poi</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <!–xls(03版本,最多65536行数据)–>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.poi</groupId>-->
<!-- <artifactId>poi</artifactId>-->
<!-- <version>3.11</version>-->
<!-- </dependency>-->
<!-- <!–xls(07版本,没有限制行数)–>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.poi</groupId>-->
<!-- <artifactId>poi-ooxml</artifactId>-->
<!-- <version>3.11</version>-->
<!-- </dependency>-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>4.12</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
第二步:新增一个DemoData类
package com.example.demo.easyExcel;
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;
}
第三步:我们就可以直接向Excel写入数据了,一行代码即可,这边把效果图也贴出来
package com.example.demo.easyExcel;
import com.alibaba.excel.EasyExcel;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class EasyTest {
String PATH = "C:\\GitEE\\excel-poi\\";
// 写入数据的方法
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 + "EasyTest.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
}
第四步:读数据的准备,我们根据官网来
1、官网把持久层都写好了,我们这边也新建一个DemoDAO
package com.example.demo.easyExcel;
import java.util.List;
/**
* 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
**/
public class DemoDAO {
public void save(List<DemoData> list) {
// 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
}
}
2、新建一个DemoDataListener监听类,主要注意invoke()方法
package com.example.demo.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) {
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("存储数据库成功!");
}
}
3、现在就可以开始读数据了,一行代码就行,这边把代码和效果图都贴出来
package com.example.demo.easyExcel;
import com.alibaba.excel.EasyExcel;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class EasyTest {
String PATH = "C:\\GitEE\\excel-poi\\";
/**
* 最简单的读
* <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 + "EasyTest.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
}