2015-09-21 4 views
4

Я собираюсь экспортировать большой объем данных (115 000 строк x 30 столбцов) в формате Excel OpenXML (xlsx). Я использую некоторые библиотеки, такие как DocumentFormat.OpenXML, ClosedXML, NPOI.Экспорт большого количества данных из XLSX - OutOfMemoryException

С каждым из них исключается OutOfMemoryException, потому что представление листа в памяти вызывает увеличение экспоненциальной памяти.

Также, закрывая файл документа каждые 1000 раз (и освобождая память), следующая загрузка вызывает увеличение памяти.

Есть ли более эффективный способ экспортировать данные в xlsx, не занимая много памяти?

+0

Возможно таблица HTML сохраняется как .xls? –

+0

Нет, мне нужен чистый файл xlsx! –

ответ

14

SDK для OpenXML - это правильный инструмент для этой работы, но вам нужно быть осторожным, чтобы использовать подход SAX (простой API для XML), а не подход 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; 
} 
+1

Это лучшее решение когда-либо !!!! Файл экспорта 500 000 x 800 столбцов занимает среднюю память 60MB –

+0

Я рад, что смог помочь @GianluigiLiguori – petelids

+0

@petelids пробовал ваш код и работает! Библиотеки, такие как EPPlus, CsvHelper + CsvHelper.Excel, терпят неудачу или имеют утечки памяти. Вы ссылаетесь на информацию для метода GetColumnName (почему эти цифры? ...) или метод протестирован правильно? Большое спасибо – Riga

0

Похоже, вы используете электронную таблицу, в которой должна использоваться база данных. У этого есть свои ограничения, и это может быть легко одним из них. Читайте дальше только в том случае, если вам абсолютно необходимо придерживаться существующего решения. Однако я не рекомендую его. Потому что есть еще один вопрос: если Excel не может сохранить такой большой файл, сможет ли он открыть такой файл?

Так что если вы не можете переключиться на платформу базы данных, и стандартные библиотеки, о которых вы упомянули выше, внутренне не способны обрабатывать такое количество данных, то, возможно, вы сами при создании большого XLSX. Я имею в виду, например, этот подход:

  1. экспортировать данные в пакетах (1000 или 10000 или что-то работает) в отдельные файлы для каждой партии
  2. создать инструмент ( (это ближе всего к ), , , , независимо от наличия твердых XML-библиотек), который объединяет отдельные файлы в один. Она включает в себя:

    1. извлечение XML из XLSX (обычно file.xlsx\xl\worksheets\sheet1.xml и file.xlsx\xl\worksheets\sharedStrings.xml)
    2. склейки этих частей вместе библиотеки манипулирования XML (это не должно врезаться на OutOfMemoryException, потому что вы больше не работаете со сложными объектами с электронными таблицами)
    3. переупаковка результат файлов обратно к основному XLSX (вы можете взять первую партию выходной файл в качестве основного XLSX)

у меня есть S дайте вам возможный способ достичь результата, но я бы этого избежал. Excel никогда не был платформой для хранения больших объемов данных. По сравнению с вышеуказанной задачей было бы легче убедить руководство в том, что настало время изменить инструменты/процессы в этой области.

+0

Я знаю, что использование Excel для хранения такого количества информации - плохая идея. Я уже использовал другой «трюк»: -Export data в CSV ed импортирует его через Microsoft Excel (но он не автоматизирован). Я также оценил решение 3, которое вы мне предоставили, но цель моего сообщения состояла в том, чтобы узнать, были ли использованы эти библиотеки другими способами или если для этого существует другая библиотека. Я думаю, что единственным решением является запись исходного документа xlsx после спецификации открытого формата документа и, таким образом, сокращение накладных расходов на объекты. P.S.: Прошу прощения за мой английский: D –

+0

Похоже, этот ответ является любимой мишенью для downvotes. – miroxlav

1

Excel способен открывать довольно большие файлы, если на вашем компьютере достаточно памяти. Это в большинстве случаев ограничивающий фактор ...

99% библиотек там не были созданы для обработки большого набора данных, и в результате у вас возникнут ошибки в памяти, если вы попытаетесь выбросить слишком много данных на их.

Некоторые из них, такие как Spout, которые я создал, были созданы для решения этой проблемы. Хитрость заключается в том, чтобы передавать данные и избегать хранения вещей в памяти. Я не уверен, какой язык вы используете (а не PHP), но для вашего языка может быть подобная библиотека.Если нет, вы можете по-прежнему взглянуть на Spout - это open-source - и преобразовать его на свой язык.

+0

Я согласен с вами в том, что проблема связана с данными потока или указывать на файл (избегая загружать полное представление листа в память). Ваш носик действительно интересен, но, к сожалению, язык, который я использую, - это C#, и портирование было бы слишком экспансивным для меня. –

+0

@GianluigiLiguori - возможно, есть способ установить PHP и напрямую использовать библиотеку. – miroxlav

+0

Очевидно, но я ищу собственное решение .NET. –

Смежные вопросы