Java 导出大数据量Excel 使用 POI 和 EasyExcel 实战

个人理解:

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>

<!--        &lt;!&ndash;xls(03版本,最多65536行数据)&ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>org.apache.poi</groupId>-->
<!--            <artifactId>poi</artifactId>-->
<!--            <version>3.11</version>-->
<!--        </dependency>-->

<!--        &lt;!&ndash;xls(07版本,没有限制行数)&ndash;&gt;-->
<!--        <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();
    }
}

在这里插入图片描述


参考文献:B站up主 狂神说JAVA

个人比较喜欢看他的学习视频

欢迎讨论:QQ 770850769

猜你喜欢

转载自blog.csdn.net/weixin_45452416/article/details/110003835