玩转POI、EasyExcel报表导入导出!

本文介绍了Apache POI处理Excel的03和07版本的区别,以及在处理大文件时的不同策略,包括HSSF、XSSF和SXSSF。此外,还详细展示了阿里巴巴的EasyExcel在Excel读写上的简洁使用方式,强调其节省内存和简便操作的特点。
摘要由CSDN通过智能技术生成


前言

开发中经常会涉及到excel的处理,如导出Excel到数据库中!,操作Excel目前比较流行的就是Apache POI和阿里巴巴的easyExcel。最近小编在公司有这么一个需求,需要将公司的员工信息导入和导出,那么怎么去实现这个功能呢?市面上有上面两种工具可以实现,下面我们一一来编码实现对比一下,到底哪个更好?

小编整理了源码,都放在Gitee上面:https://gitee.com/summerydf/excel-to-import-export/tree/master


一、POI操作Excel

1.1 导入依赖包

<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>

1.2 编写测试代码:

/**
 * @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版本中的数据");
    }
}

1.3 03和07版本的区别

03和07版本的区别读和写,对象不同,方法是一样的。另外03版最多65536行,07行数没有限制。03版本大数据量导入超过65536行,会导致内存溢出,产生OOM异常。

二、大文件写入

2.1 大文件写HSSF

缺点:最多只能处理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);
}

2.2 大文件写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 < 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);
}

2.3 大文件写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版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。

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

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

三、EasyExcel操作Excel

3.1 导入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.0-beta2</version>
</dependency>

3.2 Excel写入

@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());
    }
}

测试结果:
在这里插入图片描述

3.3 Excel读取

/**
* 最简单的读
* <p>1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <p>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处理框架,因为没有一次全部加载进内存,是从磁盘上一行行解析,所以节省内存,同时操作十分简便,一行代码,可以根据实体类自动生成表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DT辰白

你的鼓励是我创作的源泉

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值