.net 从XLSX导出大量数据-OutOfMemoryException
我即将以Excel OpenXML格式(xlsx)导出大量数据(115000行x30列)。 我正在使用一些库,如DocumentFormat.OpenXML、ClosedXML、NPOI 每次执行此操作时,都会抛出OutOfMemoryException,因为内存中的工作表表示会导致内存呈指数级增长 另外,每1000行关闭一次文档文件(并释放内存),下一次加载会导致内存增加.net 从XLSX导出大量数据-OutOfMemoryException,.net,openxml,xlsx,closedxml,vb.net,vba,c#,python,java,.net,Openxml,Xlsx,Closedxml,Vb.net,Vba,C#,Python,Java,我即将以Excel OpenXML格式(xlsx)导出大量数据(115000行x30列)。 我正在使用一些库,如DocumentFormat.OpenXML、ClosedXML、NPOI 每次执行此操作时,都会抛出OutOfMemoryException,因为内存中的工作表表示会导致内存呈指数级增长 另外,每1000行关闭一次文档文件(并释放内存),下一次加载会导致内存增加 有没有一种更高效的方法可以在不占用大量内存的情况下以xlsx格式导出数据?看起来您使用的是必须使用数据库的电子表格。它有其
有没有一种更高效的方法可以在不占用大量内存的情况下以xlsx格式导出数据?看起来您使用的是必须使用数据库的电子表格。它有其局限性,这很容易成为其中之一。如果您绝对需要坚持现有解决方案,请进一步阅读。不过,我不建议这样做。因为还有一个问题:如果Excel无法保存这么大的文件,它能打开这样的文件吗? 因此,如果您无法切换到数据库平台,并且上面提到的标准库在内部无法处理如此大量的数据,那么在创建大型XLSX时,您可能需要自己处理。我的意思是,例如,这种方法:
file.XLSX\xl\worksheets\sheet1.XML
和file.XLSX\xl\worksheets\sharedStrings.XML
)我已经向您展示了实现结果的可能方法,但我会避免这样做。Excel从来不是存储大量数据的平台。与上述任务相比,可以更容易地说服管理层,是时候更改该领域的工具/流程了。只要计算机内存充足,Excel就能够打开相当大的文件。大多数情况下,这是限制因素 99%的库都不是为处理大型数据集而构建的,如果您试图向它们抛出太多数据,那么最终会出现内存不足错误
其中一些,就像我创造的,是为了解决这个问题而创造的。诀窍是流式传输数据,避免将内容存储在内存中。我不确定您使用的是哪种语言(似乎不是PHP),但您的语言可能有一个类似的库。如果没有,您仍然可以查看Spout—它是开源的—并将其转换为您的语言。OpenXML SDK是这项工作的合适工具,但您需要小心使用(XML的简单API)方法,而不是转换方法。从SAX的维基百科链接文章: 当DOM作为一个整体对文档进行操作时,SAX解析器按顺序对XML文档的每一部分进行操作 这大大减少了处理大型Excel文件时所消耗的内存量 这里有一篇关于它的好文章- 根据那篇文章改编,下面是一个输出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;
}
公共静态void LargeExport(字符串文件名)
{
使用(电子表格文档=电子表格文档.Create(文件名,电子表格文档类型.工作簿))
{
//写入开始元素时将使用此属性列表
列出属性;
OpenXmlWriter;
document.AddWorkbookPart();
WorksheetPart WorksheetPart=document.WorkbookPart.AddNewPart();
writer=OpenXmlWriter.Create(工作表部分);
writer.writeStart元素(新工作表());
writer.writeStarElement(新的SheetData());
对于(int rowNum=1;rowNum可能是另存为.xls的HTML表?不,我需要纯xlsx文件!我同意您的观点,问题是流数据或指向文件(避免在内存中加载完整的工作表表示).你的喷口真的很有趣,但不幸的是,我使用的语言是C#,移植对我来说太过膨胀了me@GianluigiLiguori–也许有一种方法可以安装PHP并直接使用该库。显然,我正在寻找本机的.NET解决方案。您是否尝试过这种方法:?它是由Microsoft构建的,似乎支持大型电子表格是的,它会导致同样的问题:ClosedXML本身在后台使用OpenXML这是有史以来最好的解决方案!!!!导出500.000行x 400列的文件占用60MB的平均内存我很高兴我能提供帮助@GianluigiLiguori@petelids已尝试您的代码并运行!像EPPlus、CsvHelper+CsvHelper.Excel这样的库失败或内存泄漏。您是否引用了这些信息对于方法GetColumnName(为什么使用这些数字?…)还是正确测试的方法?非常感谢@Riga,我很高兴它对您有效。我很确定GetColumnName
是正确的,但我还没有编写一系列测试来确认它。@Riga-26
只是字母表中的字母数;当我们到达Z
时,下一列变成AA
,然后从AZ
我们转到BA
等。65
是A
的ASCII值……我没有任何其他答案可以解释该逻辑,但我有一个答案可以从另一个角度解释它(即,将单元格引用转换为列索引)这可能有助于解释更多的事情。这是可以找到的。看起来这个答案是反对票最喜欢的目标。