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

简介: 大家好,我是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的热门话题分析
Apsara Clouder大数据专项技能认证配套课程:基于MaxCompute的热门话题分析
相关文章
|
4月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
149 4
|
4月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
5月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
685 3
|
5月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
820 17
|
6月前
|
存储 监控 算法
Java垃圾回收机制(GC)与内存模型
本文主要讲述JVM的内存模型和基本调优机制。
|
6月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
6月前
|
边缘计算 算法 Java
Java 绿色计算与性能优化:从内存管理到能耗降低的全方位优化策略与实践技巧
本文探讨了Java绿色计算与性能优化的技术方案和应用实例。文章从JVM调优(包括垃圾回收器选择、内存管理和并发优化)、代码优化(数据结构选择、对象创建和I/O操作优化)等方面提出优化策略,并结合电商平台、社交平台和智能工厂的实际案例,展示了通过Java新特性提升性能、降低能耗的显著效果。最终指出,综合运用这些优化方法不仅能提高系统性能,还能实现绿色计算目标,为企业节省成本并符合环保要求。
253 0
|
算法 Java 开发者
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
167 0
|
算法 Java
【Java 虚拟机原理】垃圾回收算法 ( Java 虚拟机内存分区 | 垃圾回收机制 | 引用计数器算法 | 引用计数循环引用弊端 )
【Java 虚拟机原理】垃圾回收算法 ( Java 虚拟机内存分区 | 垃圾回收机制 | 引用计数器算法 | 引用计数循环引用弊端 )
255 0
|
存储 Java 程序员
java-jvm-内存分区
 学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆、栈以及静态数据区。那么在Java语言当中,内存又是如何划分的呢?   由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分。
1620 0

热门文章

最新文章