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

Groovy转换XLSX到CSV遇GC overhead limit exceeded错误求助

解决Groovy处理大XLSX文件时的OOM(GC Overhead Limit Exceeded)问题

Hey there, let's fix that out-of-memory issue you're running into with large XLSX files in your Groovy script. We've got two key things to address here: adjusting how much memory Groovy can use, and rewriting your code to avoid loading the entire Excel file into memory at once. Let's dive in.

1. 给Groovy脚本分配更多堆内存

First off, if you're running your script from the command line, you can pass a VM argument to increase the maximum heap size directly:

groovy -Xmx2g your-script-name.groovy

The -Xmx2g flag sets the maximum heap memory to 2 gigabytes. Feel free to bump this up to -Xmx4g or -Xmx8g if your file is extra large—just match it to what your system can handle.

If you're using an IDE like IntelliJ IDEA to run the script:

  • Go to Run > Edit Configurations
  • Find your Groovy script's configuration
  • In the VM options field, enter -Xmx2g (or a larger value)
  • Save the config and re-run your script

2. 优化代码:用POI的SAX流式解析处理大文件

While increasing memory can work for slightly larger files, it's not a long-term fix for huge spreadsheets. The real solution is to use POI's SAX-based streaming parser, which reads the Excel file line by line instead of loading everything into memory at once. This keeps your memory usage super low, even for massive files.

Here's a revised version of your script using this approach:

@Grapes([
    @Grab(group='org.apache.poi', module='poi-ooxml', version='3.9'),
    @Grab(group='org.apache.poi', module='poi', version='3.9')
])
import org.apache.poi.openxml4j.opc.OPCPackage
import org.apache.poi.xssf.eventusermodel.XSSFReader
import org.apache.poi.xssf.model.SharedStringsTable
import org.apache.poi.xssf.usermodel.XSSFRichTextString
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

class ExcelToCsvHandler extends DefaultHandler {
    private SharedStringsTable sst
    private String currentContent
    private boolean isCellValue
    private PrintWriter printWriter
    private int lastCol = -1
    private boolean firstCellInRow = true

    ExcelToCsvHandler(SharedStringsTable sst, PrintWriter printWriter) {
        this.sst = sst
        this.printWriter = printWriter
    }

    @Override
    void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if ("c".equals(qName)) {
            // Check if this cell holds a shared string
            String cellType = attributes.getValue("t")
            isCellValue = "s".equals(cellType) || "str".equals(cellType)
            
            // Calculate current column index
            String cellRef = attributes.getValue("r")
            int currentCol = cellRef.replaceAll("[^0-9]", "").toInteger() - 1
            
            // Fill empty columns between last cell and current cell
            if (!firstCellInRow) {
                for (int i = lastCol + 1; i < currentCol; i++) {
                    printWriter.print("\"\";")
                }
            }
            lastCol = currentCol
        }
        currentContent = ""
    }

    @Override
    void characters(char[] ch, int start, int length) throws SAXException {
        if (isCellValue) {
            currentContent += new String(ch, start, length)
        }
    }

    @Override
    void endElement(String uri, String localName, String qName) throws SAXException {
        if ("v".equals(qName)) {
            // Resolve cell value (handle shared strings)
            String value = isCellValue 
                ? new XSSFRichTextString(sst.getEntryAt(currentContent.toInteger())).toString() 
                : currentContent
            
            // Escape double quotes in value for valid CSV
            value = value.replace('"', '""')
            printWriter.print("\"${value}\";")
            
            isCellValue = false
            firstCellInRow = false
        } else if ("row".equals(qName)) {
            // End of row, add newline and reset column tracking
            printWriter.println()
            lastCol = -1
            firstCellInRow = true
        }
    }
}

def convertXlsxToCsv(String inputPath) {
    OPCPackage pkg = OPCPackage.open(inputPath)
    try {
        XSSFReader reader = new XSSFReader(pkg)
        SharedStringsTable sharedStrings = reader.getSharedStringsTable()

        XMLReader parser = XMLReaderFactory.createXMLReader()
        parser.setContentHandler(new ExcelToCsvHandler(sharedStrings, new PrintWriter(System.out)))

        // Iterate through all sheets in the workbook
        XSSFReader.SheetIterator sheetIterator = reader.getSheetsData()
        while (sheetIterator.hasNext()) {
            InputStream sheetStream = sheetIterator.next()
            String sheetName = sheetIterator.getSheetName()
            
            println sheetName
            parser.parse(new InputSource(sheetStream))
            
            sheetStream.close()
        }
    } finally {
        pkg.close()
    }
}

convertXlsxToCsv("venrx.xlsx")

Quick breakdown of the optimized code:

  • The ExcelToCsvHandler class handles SAX parsing events, which lets us process each cell and row as we encounter them, without loading the whole file
  • It handles shared strings (a common optimization in XLSX files) and properly escapes double quotes for valid CSV output
  • We iterate through each sheet's data stream instead of loading the entire workbook into memory

Why your original code hit an OOM error

Your original code used WorkbookFactory.create(), which loads every sheet, row, and cell from the Excel file into memory all at once. A 20MB XLSX file can expand to hundreds of megabytes (or even gigabytes) in memory because of how POI stores the data internally. This quickly overwhelms the default heap size and triggers the GC overhead limit error.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:37:54