7

我即将以 Excel OpenXML 格式 (xlsx) 导出大量数据(115.000 行 x 30 列)。我正在使用一些库,如 DocumentFormat.OpenXML、ClosedXML、NPOI。

对于每一个,都会引发 OutOfMemoryException,因为内存中工作表的表示会导致内存呈指数增长。

同样每 1000 行关闭文档文件(并释放内存),下一次加载会导致内存增加。

有没有更高效的方式来导出 xlsx 中的数据而不占用大量内存?

4

3 回答 3

24

OpenXML SDK 是完成这项工作的正确工具,但您需要小心使用SAX(XML 的简单 API)方法而不是DOM方法。来自 SAX 的链接维基百科文章:

DOM 对整个文档进行操作,而 SAX 解析器则按顺序对 XML 文档的每一部分进行操作

大大减少了处理大型 Excel 文件时消耗的内存量。

这里有一篇很好的文章 - http://polymathprogrammer.com/2012/08/06/how-to-properly-use-openxmlwriter-to-write-large-excel-files/

改编自那篇文章,这是一个输出 115k 行和 30 列的示例:

public static void LargeExport(string filename)
{
    using (SpreadsheetDocument document = SpreadsheetDocument.Create(filename, SpreadsheetDocumentType.Workbook))
    {
        //this list of attributes will be used when writing a start element
        List<OpenXmlAttribute> attributes;
        OpenXmlWriter writer;

        document.AddWorkbookPart();
        WorksheetPart workSheetPart = document.WorkbookPart.AddNewPart<WorksheetPart>();

        writer = OpenXmlWriter.Create(workSheetPart);            
        writer.WriteStartElement(new Worksheet());
        writer.WriteStartElement(new SheetData());

        for (int rowNum = 1; rowNum <= 115000; ++rowNum)
        {
            //create a new list of attributes
            attributes = new List<OpenXmlAttribute>();
            // add the row index attribute to the list
            attributes.Add(new OpenXmlAttribute("r", null, rowNum.ToString()));

            //write the row start element with the row index attribute
            writer.WriteStartElement(new Row(), attributes);

            for (int columnNum = 1; columnNum <= 30; ++columnNum)
            {
                //reset the list of attributes
                attributes = new List<OpenXmlAttribute>();
                // add data type attribute - in this case inline string (you might want to look at the shared strings table)
                attributes.Add(new OpenXmlAttribute("t", null, "str"));
                //add the cell reference attribute
                attributes.Add(new OpenXmlAttribute("r", "", string.Format("{0}{1}", GetColumnName(columnNum), rowNum)));

                //write the cell start element with the type and reference attributes
                writer.WriteStartElement(new Cell(), attributes);
                //write the cell value
                writer.WriteElement(new CellValue(string.Format("This is Row {0}, Cell {1}", rowNum, columnNum)));

                // write the end cell element
                writer.WriteEndElement();
            }

            // write the end row element
            writer.WriteEndElement();
        }

        // write the end SheetData element
        writer.WriteEndElement();
        // write the end Worksheet element
        writer.WriteEndElement();
        writer.Close();

        writer = OpenXmlWriter.Create(document.WorkbookPart);
        writer.WriteStartElement(new Workbook());
        writer.WriteStartElement(new Sheets());

        writer.WriteElement(new Sheet()
        {
            Name = "Large Sheet",
            SheetId = 1,
            Id = document.WorkbookPart.GetIdOfPart(workSheetPart)
        });

        // End Sheets
        writer.WriteEndElement();
        // End Workbook
        writer.WriteEndElement();

        writer.Close();

        document.Close();
    }
}

//A simple helper to get the column name from the column index. This is not well tested!
private static string GetColumnName(int columnIndex)
{
    int dividend = columnIndex;
    string columnName = String.Empty;
    int modifier;

    while (dividend > 0)
    {
        modifier = (dividend - 1) % 26;
        columnName = Convert.ToChar(65 + modifier).ToString() + columnName;
        dividend = (int)((dividend - modifier) / 26);
    }

    return columnName;
}
于 2015-09-25T17:01:29.017 回答
1

只要您的计算机中有足够的内存,Excel 就能够打开相当大的文件。这是大多数时候的限制因素......

99% 的库都不是为处理大型数据集而构建的,如果你试图向它们扔太多数据,你最终会出现内存不足的错误。

其中一些,比如我创建的Spout ,就是为了解决这个问题而创建的。诀窍是流式传输数据并避免将内容存储在内存中。我不确定您使用的是哪种语言(似乎不是 PHP),但您的语言可能有类似的库。如果没有,您仍然可以查看 Spout - 它是开源的 - 并将其转换为您的语言。

于 2015-09-21T17:08:36.360 回答
-1

看起来您正在使用必须使用数据库的电子表格。它有其局限性,这很容易成为其中之一。仅在您绝对需要坚持现有解决方案的情况下进一步阅读。但是,我不推荐它。因为还有一个问题:如果Excel无法保存这么大的文件,那它还能打开这么大的文件吗?

因此,如果您无法切换到上面提到的数据库平台和标准库,并且在内部无法处理如此大量的数据,那么在创建大型 XLSX 时,您可能只能靠自己了。我的意思是例如这种方法:

  1. 将您的数据分批(1,000 或 10,000 或任何有效)导出到每个批次的单独文件
  2. 创建一个工具((这是最接近)、,任何具有可靠 XML 库的工具),它将单独的文件合并为一个。它涉及:

    1. 从 XLSX 中提取 XML(通常是file.xlsx\xl\worksheets\sheet1.xmlfile.xlsx\xl\worksheets\sharedStrings.xml
    2. 通过 XML 操作库将这些部分粘合在一起(这不应该在 OutOfMemoryException 上崩溃,因为您不再使用复杂的电子表格对象)
    3. 将结果文件重新打包回主 XLSX(您可以将第一批输出文件作为主 XLSX)

我已经向您展示了实现结果的可能方法,但我会避免这种情况。Excel 从来都不是存储大量数据的平台。与上述任务相比,说服管理层认为是时候改变该领域的工具/流程可能更容易。

于 2015-09-21T10:38:18.400 回答