You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:Java读取超30MB大XLSX文件并完整复制内容的技术问题

Java 基于SAX事件API的大Excel复制方案

核心思路

用POI的SAX解析器逐行读取源Excel,同时搭配SXSSFWorkbook(流式写入)生成新文件,既避免内存溢出,又能完整保留空单元格。

关键实现步骤

  • 解析单元格位置(如A1、B2)计算行号和列号,通过位置差补全空单元格
  • 维护当前行状态,行切换时补全上一行的尾部空单元格
  • 区分字符串(SST索引)、数字等单元格类型,正确提取值;空单元格直接写入空字符串

完整代码示例

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.FileOutputStream;
import java.io.InputStream;

public class LargeExcelCopy {
    private SharedStringsTable sst;
    private SXSSFWorkbook targetWorkbook;
    private Sheet targetSheet;
    private Row currentTargetRow;
    private int currentRowNum = -1;
    private int lastColNum = -1;
    private String lastContents;
    private boolean isNextString;

    public void copy(String sourcePath, String targetPath) throws Exception {
        // 初始化流式写入工作簿,仅保留100行在内存,其余刷入磁盘
        targetWorkbook = new SXSSFWorkbook(100);
        targetSheet = targetWorkbook.createSheet("复制结果");

        OPCPackage pkg = OPCPackage.open(sourcePath);
        XSSFReader reader = new XSSFReader(pkg);
        sst = reader.getSharedStringsTable();

        XMLReader parser = XMLReaderFactory.createXMLReader();
        parser.setContentHandler(new ExcelHandler());

        // 读取第一个工作表(多表需遍历reader.getSheetsData())
        InputStream sheetStream = reader.getSheetsData().next();
        parser.parse(new InputSource(sheetStream));
        sheetStream.close();

        // 写入并清理资源
        try (FileOutputStream out = new FileOutputStream(targetPath)) {
            targetWorkbook.write(out);
        } finally {
            targetWorkbook.dispose();
            pkg.close();
        }
    }

    private class ExcelHandler extends DefaultHandler {
        @Override
        public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
            lastContents = "";
            if ("c".equals(name)) {
                CellReference cr = new CellReference(attributes.getValue("r"));
                int rowNum = cr.getRow();
                int colNum = cr.getCol();

                // 行切换时补全上一行空单元格,创建新行
                if (rowNum != currentRowNum) {
                    if (currentRowNum != -1) {
                        fillRowEmptyCells(currentRowNum, lastColNum);
                    }
                    currentRowNum = rowNum;
                    currentTargetRow = targetSheet.createRow(currentRowNum);
                    lastColNum = -1;
                }

                // 补全当前行中间的空单元格
                if (colNum > lastColNum + 1) {
                    for (int i = lastColNum + 1; i < colNum; i++) {
                        currentTargetRow.createCell(i).setCellValue("");
                    }
                }
                lastColNum = colNum;

                // 判断是否为SST字符串类型
                isNextString = "s".equals(attributes.getValue("t"));
            }
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            lastContents += new String(ch, start, length);
        }

        @Override
        public void endElement(String uri, String localName, String name) throws SAXException {
            if ("v".equals(name)) {
                String value = lastContents;
                if (isNextString) {
                    try {
                        int idx = Integer.parseInt(value);
                        value = sst.getItemAt(idx).getString();
                    } catch (NumberFormatException e) {
                        value = "";
                    }
                }
                currentTargetRow.createCell(lastColNum).setCellValue(value);
            }
        }

        // 补全行尾空单元格(可根据源表最大列数调整)
        private void fillRowEmptyCells(int rowNum, int lastCol) {
            // 示例:若已知源表最大列数为MAX_COL,取消注释以下代码
            // int MAX_COL = 20;
            // for (int i = lastCol + 1; i < MAX_COL; i++) {
            //     targetSheet.getRow(rowNum).createCell(i).setCellValue("");
            // }
        }
    }

    public static void main(String[] args) throws Exception {
        new LargeExcelCopy().copy("source.xlsx", "target.xlsx");
    }
}

C# 大Excel复制方案(EPPlus)

转用C#的话,EPPlus的流式API更简洁,自动处理空单元格,内存占用极低。

代码示例

using OfficeOpenXml;
using System.IO;

class Program
{
    static void Main()
    {
        ExcelPackage.LicenseContext = LicenseContext.NonCommercial;

        string sourcePath = "source.xlsx";
        string targetPath = "target.xlsx";

        using (var sourcePackage = new ExcelPackage(new FileInfo(sourcePath)))
        using (var targetPackage = new ExcelPackage(new FileInfo(targetPath)))
        {
            var sourceSheet = sourcePackage.Workbook.Worksheets[0];
            var targetSheet = targetPackage.Workbook.Worksheets.Add("复制结果");

            // 流式读取并复制整表,自动保留空单元格
            var sourceRange = sourceSheet.Cells[1, 1, sourceSheet.Dimension.End.Row, sourceSheet.Dimension.End.Column];
            targetSheet.Cells["1:" + sourceSheet.Dimension.End.Row].LoadFromRange(sourceRange, true);

            targetPackage.Save();
        }
    }
}

内容的提问来源于stack exchange,提问作者poojay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 10:55:21