XLSX изнутри: почему потоковая запись решает почти все проблемы
Чтобы понять, почему потоковая запись в XLSX-файл настолько эффективна, нужно понять, что XLSX — это обычный ZIP-архив. Если переименовать его в .zip и распаковать, внутри мы увидим множество xml-файлоа, примерно так:
- [Content_Types].xml
- xl/workbook.xml
- xl/styles.xml
- xl/sharedStrings.xml
- xl/worksheets/sheet1.xml
И если учесть, что ни один из этих файлов не требует знания всех данных заранее, то именно это делает потоковую работу возможной.
Данные в XLSX и что с ними делает объектная модель
Основные данные листа хранятся в xl/worksheets/sheet1.xml.
Упрощённо он выглядит так:
<worksheet>
<sheetData>
<row r="1">
<c r="A1"><v>Header 1</v></c>
<c r="B1"><v>Header 2</v></c>
</row>
<row r="2">
<c r="A2"><v>Value 1</v></c>
<c r="B2"><v>Value 2</v></c>
</row>
</sheetData>
</worksheet>
Ключевой момент: строки идут строго последовательно, а Excel не требует ни знать количество строк заранее, ни хранить предыдущие строки в памяти, ни иметь доступ ко всем данным сразу.
Но большинство популярных библиотек работают так:
- Создают объект Workbook
- Создают объект Worksheet
- Для каждой строки создают объект Row
- Для каждой ячейки создают объект Cell
Все объекты живут в памяти до сохранения файла. При 300 000 строк × 10 колонок - это 3 миллиона объектов ячеек, плюс строки, плюс стили, плюс много чего еще. Даже если каждая ячейка занимает всего несколько сотен байт, то счёт, в итоге, идёт на гигабайты.
Потоковая модель: что меняется принципиально
Потоковая запись делает одну простую вещь: пишет XML сразу в файл и забывает о нём. И никаких объектов книги, никакой «модели документа», никакого хранения данных в памяти.
Память используется только на текущую строку, на текущие стили, на служебные буферы.
Тут отмечу, что в XLSX стили вынесены в отдельный файл — styles.xml.
Пример:
<cellXfs count="3">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
<xf numFmtId="14" fontId="0" fillId="0" borderId="0"/>
<xf numFmtId="0" fontId="1" fillId="0" borderId="0"/>
</cellXfs>
В ячейке хранится только индекс:
<c r="A2" s="1"><v>45234</v></c>
Это означает, что стиль создаётся один раз, а дальше используется по индексу, и Excel сам всё связывает. Поэтому потоковая запись не мешает стилям, она просто требует кешировать стили, переиспользовать индексы и не создавать дубликаты.
Почему это не универсальное решение
Есть кейсы, где потоковая модель не подходит. Например, если нужно редактировать произвольные ячейки, «прыгать» по листу.
Однако, если речь идет о генерации сравнительно небольших XLSX-файлов, когда хранение всех данных в памяти не является проблемой, то и тут FastExcelWriter вполне справляется с этой задачей - библиотеку можно перевести в режим «прямой» записи в произвольные ячейки, и тогда данные будут накапливаться в мамяти, а по окончании построчно будут записаны в файл.
Но об этом будет рассказано в следующих публикациях.