开发中经常会涉及到excel的处理,如导出Excel到数据库中!,操作Excel目前比较流行的就是Apache POI和阿里巴巴的easyExcel。最近小编在公司有这么一个需求,需要将公司的员工信息导入和导出,那么怎么去实现这个功能呢?市面上有上面两种工具可以实现,下面我们一一来编码实现对比一下,到底哪个更好?
小编整理了源码,都放在Gitee上面:https://gitee.com/summerydf/excel-to-import-export/tree/master
<dependencies>
<!--03-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<!--07-->
<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>
<!--test-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
/**
* @description: Excel的POI操作
* @author: ydf
* @date: 2021/4/13 14:13
* @version: v1.0
*/
public class ExcelWriteTest {
/**
* excel文件路径
*/
private final static String PATH = "E:\\dtproject\\export-demo\\document\\";
public static void main(String[] args) throws IOException {
// 03,写入数据
testWrite03();
// 07,写入数据
testWrite07();
// 03,读取数据
testRead03();
// 07,读取数据
testRead07();
}
/**
* 03版本 ,向excel中写入数据
* @throws IOException
*/
private static void testWrite03() throws IOException {
// 1、创建一个工作簿
Workbook workbook = new HSSFWorkbook();
// 2.创建表名
Sheet sheet = workbook.createSheet("03版_dt统计表");
// 3.创建行
Row row0 = sheet.createRow(0);
// 4.创建单元格
Cell cell = row0.createCell(0);
// 5.写入数据
cell.setCellValue("这是第一行一列的格子");
// 6.创建流用于输出
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "03版本表.xls");
// 7.输出
workbook.write(fileOutputStream);
System.out.println("03版本表已经生成");
}
/**
* 07版本 ,向excel中写入数据
* @throws IOException
*/
public static void testWrite07() throws IOException {
// 1.创建工作簿
Workbook workbook = new XSSFWorkbook();
// 2.创建表名
Sheet sheet = workbook.createSheet("07版_dt统计表");
// 3.创建行
Row row0 = sheet.createRow(0);
// 4.创建单元格
Cell cell = row0.createCell(0);
// 5.写入数据
cell.setCellValue("这是第一行一列的格子");
// 6.创建流用于输出
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "07版本表.xlsx");
// 7.输出
workbook.write(fileOutputStream);
System.out.println("07版本表已经生成");
}
/**
* 03版本 ,读excel中的数据
* @throws IOException
*/
public static void testRead03() throws IOException {
// 1.创建流用于读取
FileInputStream fileInputStream = new FileInputStream(PATH + "03版本表.xls");
// 2.工作簿用于接收
Workbook workbook = new HSSFWorkbook(fileInputStream);
// 3.获取表
Sheet sheet = workbook.getSheetAt(0);
// 4.获取行
Row row = sheet.getRow(0);
// 5.获取单元格
Cell cell = row.getCell(0);
// 6.获取单元格中的数据,并输出
System.out.println(cell.getStringCellValue());
System.out.println("获取到03版本中的数据");
}
/**
* 07版本 ,读excel中的数据
* @throws IOException
*/
public static void testRead07() throws IOException {
// 1.创建流用于读取
FileInputStream fileInputStream = new FileInputStream(PATH + "07版本表.xlsx");
// 2.工作簿用于接收
Workbook workbook = new XSSFWorkbook(fileInputStream);
// 3.获取表
Sheet sheet = workbook.getSheetAt(0);
// 4.获取行
Row row = sheet.getRow(0);
// 5.获取单元格
Cell cell = row.getCell(0);
// 6.获取单元格中的数据,并输出
System.out.println(cell.getStringCellValue());
System.out.println("获取到07版本中的数据");
}
}
03和07版本的区别读和写,对象不同,方法是一样的。另外03版最多65536行,07行数没有限制。03版本大数据量导入超过65536行,会导致内存溢出,产生OOM异常。
缺点:最多只能处理65536行,否则会抛出异常。
优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度快。
java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)
@Test
public void testWrite03BigData() throws IOException {
// 时间
long begin = System.currentTimeMillis();
// 创建一个薄
Workbook workbook = new HSSFWorkbook();
// 创建表
Sheet sheet = workbook.createSheet();
// 写入数据
for (int rowNum = 0; rowNum < 65537; 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((double) (end-begin)/1000);
}
缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如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 < 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((double) (end-begin)/1000);
}
优点:可以写非常大的数据量,如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版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。
请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释…仍然只存储在内存中,因此如果广泛使用,可能需要大量内存。
再使用 POI的时候!内存问题 Jprofile!
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.0-beta2</version>
</dependency>
@Data
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
/**
* @description: 测试Excel写入
* @author: ydf
* @date: 2021/4/13 15:27
* @version: v1.0
*/
public class EasyTest {
private static final String PATH ="E:\\dtproject\\export-demo\\document\\";
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
*/
@Test
public void simpleWrite() {
// 写法1
String fileName = PATH + "EasyTest.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// write (fileName, 格式类)
// sheet (表明)
// doWrite (数据)
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
}
/**
* 最简单的读
* 1. 创建excel对应的实体对象 参照{@link DemoData}
*
2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
*
3. 直接读即可
*/
@Test
public void simpleRead() {
// 读取文件
String fileName = PATH + "EasyTest.xlsx";
// 这里需要指定读用哪个class去读,然后读取第一个sheet,文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
/**
* 有个很重要的点 DemoDataListener不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
*/
public class DemoDataListener extends AnalysisEventListener<DemoData> {
/**
* 每隔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
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
System.out.println("解析到一条数据:"+data);
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 数据解析完成了,都会来调用
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("所有数据解析完成!");
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
}
/**
* 调用接口。存储至数据库
*/
private void saveData() {
System.out.println(list.size()+">>>条数据,开始存储数据库!");
demoDAO.save(list);
System.out.println("存储数据库成功!");
}
}
/**
* 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
* 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
**/
public class DemoDAO {
public void save(List<DemoData> list) {
System.out.println("=========写入数据库数据============");
list.forEach(demoData -> {
System.out.println(demoData.getString());
});
}
}
EasyExcel是阿里巴巴开源的excel处理框架,因为没有一次全部加载进内存,是从磁盘上一行行解析,所以节省内存,同时操作十分简便,一行代码,可以根据实体类自动生成表。