如何在超大的Excel文件中读取前N行数据?

我的Excel文件大概有3000行,1000列。
我希望在这个海量数据中进行搜索,我尝试了使用POI,也尝试了比如先取出部分行数,如60行,并且在60行内进行搜索。但不论怎么样,我总是会遇到out of memroy的问题。

阅读 1.3k
avatarAI BotBETA

首先,处理超大的Excel文件时,遇到内存溢出(out of memory)问题是比较常见的。这是因为POI等库会将整个Excel文件加载到内存中,如果文件过大,就会占用大量内存。

如果你只需要读取前N行数据,可以考虑使用Apache的POI-OOXML库,它支持按行读取Excel文件。这样,你只需要将数据一行行地读出,而不需要将整个文件加载到内存中。

下面是一个基本的代码示例:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;

public class ReadExcel {
    public static void main(String[] args) throws IOException {
        // 文件路径
        String filePath = "your_excel_file.xlsx";
        FileInputStream fis = new FileInputStream(new File(filePath));

        // 用WorkbookFactory创建Workbook对象
        Workbook workbook = WorkbookFactory.create(fis);
        // 得到第一页(sheet)
        Sheet sheet = workbook.getSheetAt(0);
        // 得到行迭代器
        Iterator<Row> rowIterator = sheet.iterator();
        int rowCount = 0;
        while (rowIterator.hasNext() && rowCount < N) { // N为你想读取的行数
            Row row = rowIterator.next();
            // 这里你可以获取行中的列数据
            // ...
            rowCount++;
        }
    }
}

在这个代码中,我们首先打开了一个指向你的Excel文件的FileInputStream。然后我们用WorkbookFactory创建了一个Workbook对象,得到了第一个sheet,然后通过迭代器一行行地读取数据。当达到你设定的行数N时,停止读取。注意,你需要替换你的Excel文件路径以及你想读取的行数N。

2 个回答

试试看用hutool的ExcelUtil.readBySax
添加一下maven:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.4</version>
</dependency>

覆盖实现一下里面的方法:

private RowHandler createRowHandler() {
    return new RowHandler() {
        @Override
        public void handle(int sheetIndex, long rowIndex, List<Object> rowlist) {
            //遍历excel的每一行你想做什么,sheetIndex是指定sheet,rowIndex是当前行(从0开始),rowList是行数据
            //可以用通过rowIndex来限制你要读取的行范围
        }
    };
}
ExcelUtil.readBySax(file, 0, createRowHandler());

这个问题主要是因为POI的内存管理不太好,POI官方提供了SAX事件驱动模式,可以读取大文件。
EasyExcel也可以解决内存溢出的问题:

// 导入依赖包
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.Sheet;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

@Slf4j
public class ReadExcelDemo {

    // 定义要读取的前N行数据的数量
    private static final int ROW_COUNT = 1000;

    public static void main(String[] args) {
        // 定义要读取的Excel文件路径
        String fileName = "path/to/excel/file.xlsx";
        // 定义存储读取结果的列表
        List<Object> dataList = new ArrayList<>();

        // 使用EasyExcel读取Excel文件
        EasyExcel.read(fileName, new AnalysisEventListener<Object>() {
            @Override
            public void invoke(Object data, AnalysisContext context) {
                // 判断读取的行数是否超过了设定值
                if (context.readRowHolder().getRowIndex() <= ROW_COUNT) {
                    // 将读取的数据存储到列表中
                    dataList.add(data);
                } else {
                    // 超过设定值后停止读取
                    context.interrupt();
                }
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                log.info("Read {} rows of data from Excel file", context.readRowHolder().getRowIndex());
            }
        }).sheet().doRead();

        // 处理读取结果
        // ...
    }
}

如果可以接受商业软件,可以考虑使用GcExcel。GcExcel 做了相关的处理,优化了内存的使用,不会出问题。同时它的API,使用起来比较简单,API是基于Range(区域),和Excel的概念相似,也有专门的API可以查找或者替换内容。
下面是个示例代码,我们从如下图的Excel文件中进行搜索:

image.png
代码:

public void FindText() {
    // 构建 workbook
    Workbook wb = new Workbook();
    // 打开需要搜索的文件
    wb.open("resources/ReplaceExample.xlsx");
    // 打开对应的sheet
    IWorksheet sheet = wb.getWorksheets().get(0);
    FindOptions findOption = new FindOptions();
    findOption.setMatchCase(true);
    // 获取页面中包含内容的区域
    IRange searchRange = sheet.getUsedRange();

    // 存储查找到的区域
    IRange range = null;

    // 循环查找
    do {
        // 搜索包含++的格子,本例中会搜索出C++的格子区域
        range = searchRange.find("++", findOption);
        if (range != null) {
            // 从查找到的区域中取出格子的值
            String findVal = range.getValue().toString();
            // 获取到值后,进行相关操作
        }
    }
    while (range != null);
}