Java EasyExcel 导出报内存溢出如何解决

本文涉及的产品
云原生大数据计算服务 MaxCompute,5000CU*H 100GB 3个月
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: 大家好,我是V哥。使用EasyExcel进行大数据量导出时容易导致内存溢出,特别是在导出百万级别的数据时。以下是V哥整理的解决该问题的一些常见方法,包括分批写入、设置合适的JVM内存、减少数据对象的复杂性、关闭自动列宽设置、使用Stream导出以及选择合适的数据导出工具。此外,还介绍了使用Apache POI的SXSSFWorkbook实现百万级别数据量的导出案例,帮助大家更好地应对大数据导出的挑战。欢迎一起讨论!

大家好,我是 V 哥。使用EasyExcel进行大数据量导出时容易导致内存溢出,特别是在导出百万级别的数据时。你有遇到过这种情况吗,以下是V 哥整理的解决该问题的一些常见方法,分享给大家,欢迎一起讨论:

EasyExcel大数据量导出常见方法

1. 分批写入

  • EasyExcel支持分批写入数据,可以将数据分批加载到内存中,分批写入Excel文件,避免一次性将大量数据加载到内存中。
  • 示例代码

    String fileName = "large_data.xlsx";
    ExcelWriter excelWriter = EasyExcel.write(fileName).build();
    WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build();
    
    // 假设每次写入10000条数据
    int batchSize = 10000;
    List<Data> dataList;
    int pageIndex = 0;
    do {
         
        // 分页获取数据
        dataList = getDataByPage(pageIndex++, batchSize);
        excelWriter.write(dataList, writeSheet);
    } while (dataList.size() == batchSize);
    
    // 关闭资源
    excelWriter.finish();
    

2. 设置合适的JVM内存

  • 针对大数据导出场景,可以尝试增大JVM的内存分配,例如:
       java -Xms512M -Xmx4G -jar yourApp.jar
    
  • 解释
    • -Xms512M:设置初始堆大小为512MB。
    • -Xmx4G:设置最大堆大小为4GB。

3. 减少数据对象的复杂性

  • 导出数据时,尽量简化数据对象,避免不必要的嵌套和多余字段的加载,以减少对象占用的内存空间。

4. 关闭自动列宽设置

  • EasyExcel的自动列宽功能会占用大量内存,特别是在数据量较大的情况下。关闭自动列宽可以节省内存。
  • 示例代码
       EasyExcel.write(fileName)
               .registerWriteHandler(new SimpleWriteHandler()) // 不使用自动列宽
               .sheet("Sheet1")
               .doWrite(dataList);
    

5. 使用Stream导出(适合大数据)

  • 利用OutputStream分批写入数据,减少内存消耗。通过BufferedOutputStream可以进一步提高性能。
  • 示例代码
       try (OutputStream out = new BufferedOutputStream(new FileOutputStream(fileName))) {
         
           ExcelWriter excelWriter = EasyExcel.write(out).build();
           WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build();
           int pageIndex = 0;
           List<Data> dataList;
           do {
         
               dataList = getDataByPage(pageIndex++, batchSize);
               excelWriter.write(dataList, writeSheet);
           } while (dataList.size() == batchSize);
           excelWriter.finish();
       } catch (IOException e) {
         
           e.printStackTrace();
       }
    

6. 选择合适的数据导出工具

  • 如果数据量非常大,可以考虑切换到支持更高性能的导出工具(如Apache POI的SXSSFWorkbook),适合导出百万级别数据量,但配置和使用会更复杂。

亮点来了,那要如何使用 POI 的 SXSSFWorkbook来导出百万级别的数据量呢?

Apache POI的SXSSFWorkbook 实现百万级别数据量的导出案例

使用Apache POI的SXSSFWorkbook可以处理大数据量的Excel导出,因为SXSSFWorkbook基于流式写入,不会将所有数据加载到内存中,而是使用临时文件进行缓存,这样可以显著减少内存消耗,适合百万级别数据的导出。下面我们来看一个完整的实现示例。

代码如下

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class LargeDataExportExample {
   

    public static void main(String[] args) {
   
        // 文件输出路径
        String filePath = "vg_large_data_export.xlsx";

        // 导出百万级数据
        exportLargeData(filePath);
    }

    private static void exportLargeData(String filePath) {
   
        // 每次写入的批次大小
        final int batchSize = 10000;
        // 数据总条数
        final int totalRows = 1_000_000;

        // 创建SXSSFWorkbook对象,内存中只保留100行,超过的部分会写入临时文件
        SXSSFWorkbook workbook = new SXSSFWorkbook(100);
        workbook.setCompressTempFiles(true); // 启用临时文件压缩

        // 创建工作表
        Sheet sheet = workbook.createSheet("Large Data");

        // 创建标题行
        Row headerRow = sheet.createRow(0);
        String[] headers = {
   "ID", "Name", "Age"};
        for (int i = 0; i < headers.length; i++) {
   
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
        }

        int rowNum = 1; // 数据开始的行号

        try {
   
            // 按批次写入数据
            for (int i = 0; i < totalRows / batchSize; i++) {
   
                // 模拟获取每批数据
                List<Data> dataList = getDataBatch(rowNum, batchSize);

                // 将数据写入到Excel中
                for (Data data : dataList) {
   
                    Row row = sheet.createRow(rowNum++);
                    row.createCell(0).setCellValue(data.getId());
                    row.createCell(1).setCellValue(data.getName());
                    row.createCell(2).setCellValue(data.getAge());
                }

                // 处理完成一批数据后,可以选择清除缓存数据,防止内存溢出
                ((SXSSFSheet) sheet).flushRows(batchSize); // 清除已写的行缓存
            }

            // 将数据写入文件
            try (FileOutputStream fos = new FileOutputStream(filePath)) {
   
                workbook.write(fos);
            }
            System.out.println("数据导出完成:" + filePath);

        } catch (IOException e) {
   
            e.printStackTrace();
        } finally {
   
            // 关闭workbook并删除临时文件
            workbook.dispose();
        }
    }

    /**
     * 模拟分页获取数据
     */
    private static List<Data> getDataBatch(int startId, int batchSize) {
   
        List<Data> dataList = new ArrayList<>(batchSize);
        for (int i = 0; i < batchSize; i++) {
   
            dataList.add(new Data(startId + i, "Name" + (startId + i), 20 + (startId + i) % 50));
        }
        return dataList;
    }

    // 数据类
    static class Data {
   
        private final int id;
        private final String name;
        private final int age;

        public Data(int id, String name, int age) {
   
            this.id = id;
            this.name = name;
            this.age = age;
        }

        public int getId() {
   
            return id;
        }

        public String getName() {
   
            return name;
        }

        public int getAge() {
   
            return age;
        }
    }
}

来解释一下代码

  1. SXSSFWorkbookSXSSFWorkbook(100)表示内存中最多保留100行数据,超过的部分会写入临时文件,节省内存。
  2. 批次处理:通过batchSize控制每批次写入的数据量,以减少内存消耗。totalRows设置为1,000,000表示导出100万条数据。
  3. 模拟数据生成getDataBatch方法模拟分页获取数据,每次返回一批数据。
  4. 清除缓存行:每次写入一批数据后,通过flushRows(batchSize)将缓存的行从内存中清除,以控制内存占用。
  5. 压缩临时文件workbook.setCompressTempFiles(true)启用临时文件压缩,进一步减少磁盘空间占用。

需要注意的事项

  • 临时文件:SXSSFWorkbook会在系统临时文件夹中生成临时文件,需要确保磁盘空间足够。
  • 资源释放:完成数据写入后需要调用workbook.dispose()以清理临时文件。
  • 性能优化:可根据机器内存调整batchSizeSXSSFWorkbook缓存行数,避免频繁刷新和内存溢出。
相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps&nbsp;
相关文章
|
7天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
14 0
|
9天前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
22 8
|
7天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
11天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
39 5
|
9天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
9天前
|
安全 Java 程序员
Java内存模型的深入理解与实践
本文旨在深入探讨Java内存模型(JMM)的核心概念,包括原子性、可见性和有序性,并通过实例代码分析这些特性在实际编程中的应用。我们将从理论到实践,逐步揭示JMM在多线程编程中的重要性和复杂性,帮助读者构建更加健壮的并发程序。
|
6天前
|
存储 监控 算法
Java内存管理的艺术:深入理解垃圾回收机制####
本文将引领读者探索Java虚拟机(JVM)中垃圾回收的奥秘,解析其背后的算法原理,通过实例揭示调优策略,旨在提升Java开发者对内存管理能力的认知,优化应用程序性能。 ####
20 0
|
Java
Java常见内存溢出(OOM)解决方案
1,         程序计数器 一块很小的内存空间,作用是当前线程所执行的字节码的行号指示器。 2,         java栈 与程序计数器一样,java栈(虚拟机栈)也是线程私有的,其生命周期与线程相同。通常存放基本数据类型,对象引用(一个指向对象起始地址的引用指针或一个代表对象的句柄),reeturnAddress类型(指向一条字节码指令的地址) 栈区域有两种异常类型:如果
1792 0
|
Java 应用服务中间件 数据库