POI和EasyExcel区别和操作Excel

POI和EasyExcel操作Excel

常用场景

1、将用户信息导出为excel表格(导出数据... )

2、将Excel表中的信息录入到网站数据库(文件数据上传... )

开发中经常会设计到excel的处理,如导出Excel,导入Excel到数据库中!

操作Excel目前比较流行的就是Apache POI 和 阿里巴巴的 Easyexcel!

Apache POI

Apache POI 官网地址:https://poi.apache.org/会比较麻烦,比较消耗内存出现OOM(内存溢出异常)

EasyExcel

EasyExcel官网地址:https://github.com/alibaba/easyexcel

EasyExcel 是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。

EasyExcel 能大大减少占用内存的主要原因是在解析 Excel 时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

内存问题:POI = 100w先加载到内存 OOM。。写入文件 es = 1

下图是 EasyExcel 和 POI 在解析Excel时的对比图。

EasyExcel 使用POI的sax模式一行一行解析,并将一行的解析结果以观察者的模式通知处理。

官方文档:https://www.yuque.com/easyexcel/doc/easyexcel

POI操作Excel

POI-Excel写

创建项目

1、创建一个空项目test,创建普通Maven的Moudle demo-poi

2、引入依赖

java 复制代码
<dependencies>
	<!--xls(03)-->
	<dependency>
		<groupId>org.apache.poi</groupId>
		<artifactId>poi</artifactId>
		<version>3.9</version>
	</dependency>
	<!--xls(07)-->
	<dependency>
		<groupId>org.apache.poi</groupId>
		<artifactId>poi-ooxml</artifactId>
		<version>3.9</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>

03 | 07 版本的写,就是对象不同,方法一样的!

需要注意:2003版本和2007版本存在兼容性的问题!03最多只能有65536行!而07最多只能有1048576行!

1、工作簿:Workbook

2、工作表:Sheet

3、行:Row

4、列:Cell

03版本:

java 复制代码
// 1、创建一个工作簿
Workbook workbook = new HSSFWorkbook();
// 2、创建一个工作表
Sheet sheet = workbook.createSheet("角色统计表");
// 3、创建一个行 (1,1)
Row row1 = sheet.createRow(0);
// 4、创建一个单元格
Cell cell1 = row1.createCell(0);
cell1.setCellValue("角色名称");
// (1,2)
Cell cell2 = row1.createCell(1);
cell2.setCellValue("角色性别");
// (1,3)
Cell cell3 = row1.createCell(2);
cell3.setCellValue("角色创建时间");

// 创建第二行 (2,1)
Row row2 = sheet.createRow(1);
Cell cell21 = row2.createCell(0);
cell21.setCellValue("神里凌华");
// (2,2)
Cell cell22 = row2.createCell(1);
cell22.setCellValue("女");
// (2,3)
Cell cell23 = row2.createCell(2);
String time = new DateTime().toString("yyyy-mm-dd HH:mm:ss");
cell23.setCellValue(time);

String PATH = "D:/桌面/test/";
// 生成一张表 (IO 流) 03 版本就是使用 xls结尾!
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "角色统计表03.xls");
// 将IO流写入到Excel中
workbook.write(fileOutputStream);
// 刷新输出流
fileOutputStream.flush();
// 关闭输出流
fileOutputStream.close();
System.out.println("角色统计表03 生成完毕");

运行结果如下:

java 复制代码
// 1、创建一个工作簿
Workbook workbook = new XSSFWorkbook();
// 2、创建一个工作表
Sheet sheet = workbook.createSheet("角色统计表");
// 3、创建一个行 (1,1)
Row row1 = sheet.createRow(0);
// 4、创建一个单元格
Cell cell1 = row1.createCell(0);
cell1.setCellValue("角色名称");
// (1,2)
Cell cell2 = row1.createCell(1);
cell2.setCellValue("角色性别");
// (1,3)
Cell cell3 = row1.createCell(2);
cell3.setCellValue("角色创建时间");

// 创建第二行 (2,1)
Row row2 = sheet.createRow(1);
Cell cell21 = row2.createCell(0);
cell21.setCellValue("神里凌华");
// (2,2)
Cell cell22 = row2.createCell(1);
cell22.setCellValue("女");
// (2,3)
Cell cell23 = row2.createCell(2);
String time = new DateTime().toString("yyyy-mm-dd HH:mm:ss");
cell23.setCellValue(time);

String PATH = "D:/桌面/test/";
// 生成一张表 (IO 流) 07 版本就是使用 xlsx结尾!
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "角色统计表07.xlsx");
// 将IO流写入到Excel中
workbook.write(fileOutputStream);
// 刷新输出流
fileOutputStream.flush();
// 关闭输出流
fileOutputStream.close();
System.out.println("角色统计表07 生成完毕");

注意对象的区别,文件后缀!

大文件写HSSF:

java 复制代码
// 开始时间
long begin = System.currentTimeMillis();
// 创建一个簿
Workbook workbook = new HSSFWorkbook();
// 创建表
Sheet sheet = workbook.createSheet();
//写入数据
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");
String PATH = "D:/桌面/test/";
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "BigData03.xls");
workbook.write(fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
// 结束时间
long end = System.currentTimeMillis();
System.out.println((double) (end - begin) / 1000);

缺点:最多只能处理65536行,否则会抛出异常

java 复制代码
java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)

优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度块

大文件写XSSF:

缺点:写数据时速度非常慢,非常消耗内存,也会发生内存溢出,如100万条

优点:可以写较大的数据量,如20万条

java 复制代码
// 开始时间
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 fileOutputStream = new FileOutputStream(PATH + "BigData07.xlsx");
workbook.write(fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
// 结束时间
long end = System.currentTimeMillis();
System.out.println((double) (end - begin) / 1000);

大文件写SXSSF:

优点:可以写非常大的数据量,如100万条甚至更多条,写数据速度快,占用更少内存

注意:

过程中会产生临时文件,需要清理临时文件

默认由100条记录会被保存在内存中,如果超过这个数量,则最前面的数据被写入临时文件

如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook(数量)

java 复制代码
// 开始时间
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 fileOutputStream = new FileOutputStream(PATH + "BigData07S.xlsx");
workbook.write(fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
// 清除临时文件
((SXSSFWorkbook) workbook).dispose();
// 结束时间
long end = System.currentTimeMillis();
System.out.println((double) (end - begin) / 1000);

SXSSFWorkbook-来至官方的解释:实现"BigGridDemo"策略的流式XSSFWorkbook版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。

请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释...仍然只存储在内存中,因此如果广泛使用,可能需要大量内存。

再使用 POI的时候!内存问题Jprofile !

POI-Excel读

03 | 07

java 复制代码
String PATH = "D:/桌面/test/";
// 获取文件流
FileInputStream fileInputStream = new FileInputStream(PATH + "角色统计表03.xls");
// 1、创建一个工作簿。使用excel能操作的这边他都可以操作!
Workbook workbook = new HSSFWorkbook(fileInputStream);
// 2、通过工作簿获取工作表,表中的设置
Sheet sheet = workbook.getSheetAt(0);
// 获得行
Row row = sheet.getRow(0);
// 获得列
Cell cell = row.getCell(3);
// 读取值的时候,一定需要注意数据类型
// getStringCellValue 字符串类型
//        System.out.println(cell.getStringCellValue());
System.out.println(cell.getNumericCellValue());
// 关闭文件流
fileInputStream.close();

07版本

java 复制代码
String PATH = "D:/桌面/test/";
// 获取文件流
FileInputStream fileInputStream = new FileInputStream(PATH + "角色统计表07.xlsx");
// 1、创建一个工作簿。使用excel能操作的这边他都可以操作!
Workbook workbook = new XSSFWorkbook(fileInputStream);
// 2、通过工作簿获取工作表,表中的设置
Sheet sheet = workbook.getSheetAt(0);
// 获得行
Row row = sheet.getRow(0);
// 获得列
Cell cell = row.getCell(2);
// 读取值的时候,一定需要注意数据类型
// getStringCellValue 字符串类型
System.out.println(cell.getStringCellValue());
// 关闭文件流
fileInputStream.close();

EasyExcel操作Excel

导入依赖

java 复制代码
<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.22</version>
	</dependency>
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>fastjson</artifactId>
		<version>1.2.83</version>
	</dependency>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
</dependencies>

写入测试

创建写入的实体类

java 复制代码
@Data
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}

创建写入的测试类

java 复制代码
public class DemoDataWriteTest {

    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 = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".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();
    }

    private static class TestFileUtil {
        public static String getPath() {
            return "D:\\桌面\\test\\";
        }
    }
}

EasyExcel读

创建读取的实体类

java 复制代码
@Data
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}

创建读取的监听器类

java 复制代码
// 有个很重要的点 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("存储数据库成功!");
    }
}

创建读取的持久层类

java 复制代码
/**
 * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
 **/
public class DemoDAO {

    public void save(List<DemoData> list) {
        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
    }
}

创建读取的测试类

java 复制代码
public class DemoDataReadTest {
    /**
     * 最简单的读
     * <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 = TestFileUtil.getPath() + "test" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

        // 写法2:
        fileName = TestFileUtil.getPath() + "test" + File.separator + "demo.xlsx";
        ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
        ReadSheet readSheet = EasyExcel.readSheet(0).build();
        excelReader.read(readSheet);
        // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
        excelReader.finish();
    }

    private static class TestFileUtil {
        public static String getPath() {
            return "D:\\桌面\\";
        }
    }
}
相关推荐
七星静香14 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员15 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU15 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
q24985969317 分钟前
前端预览word、excel、ppt
前端·word·excel
stewie619 分钟前
在IDEA中使用Git
java·git
Elaine20239134 分钟前
06 网络编程基础
java·网络
G丶AEOM35 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201336 分钟前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀37 分钟前
LRU缓存算法
java·算法·缓存
镰刀出海40 分钟前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试