Техническа статия

Съхранение на клетки в блокове от редове и струйно записване на XLSX в HotXLS

HotXLS съхранява клетките на работен лист в компактни блокове от 256 реда, разрешава форматирането на редове, колони и правоъгълници през мързеливи наслагвания на интервали, вместо да създава обекти клетки, и струйно вкарва всеки ред направо в deflate потока на пакета при запис. Заедно тези три промени решават профила на паметта за голяма работна книга: пиковата употреба следва най-големия единичен ред, а не размера на пълното работно лист XML

Причината това да има значение е форма, с която всеки разработчик на електронни таблици рано или късно се среща. Потребител форматира цяла колона — един клик, един милион клетки — и наивен обектен модел отговаря, като заделя милион обекта клетки, за да държи един индекс на числов формат. Файлът на диск остава малък, защото форматът XLSX изразява това като единичен запис <col>. Процесът изобщо не остава малък

Защо форматирането на колона струва повече памет от запълването и?

Защото форматирането няма данни, които да оправдаят обекта. Клетка със стойност трябва да съществува някъде. Клетка, която е празна, но стилизирана, съществува само за да носи индекс на стил, а материализирането на милиони такива е класическият начин, по който Delphi приложение за електронни таблици изчерпва адресно пространство върху файл, който Excel отваря мигновено

Наслагванията на интервален стил премахват нуждата. Инструкция за форматиране на ред, колона или правоъгълник се съхранява веднъж като диапазон плюс частите на стила, които тя допринася, и се разрешава мързеливо, когато клетка в този диапазон реално бъде достъпена. Наслагванията оцеляват структурни редакции — вмъкването на ред вътре в форматиран блок премества интервала, вместо да го прездравява — и се върнат като компактни записи за колона, ред и клетка само със стил, което е точно как Excel ги записва

Диаграма на интервално стилово наслагване за HotXLS, показваща диапазон за форматиране на колона, пазен като един запис с заявени форматни части, вместо милион празни клетъчни обекти в Delphi
Формат на колона, ред или правоъгълник живее веднъж като interval overlay запис, претендиращ само частите, които притежава. Ефективният стил се сглобява мързеливо първия път, когато клетка вътре в диапазона действително бъде пипната
var
  Sheet: TXLSXWorksheet;
  State: TXLSXCellStyleState;
begin
  Sheet := Workbook.Sheets[1];
  // Индексите на стилове идват от пуловете на работната книга, напр. от клетка
  // която вече сте форматирали по начина, по който искате да изглежда диапазонът
  State := BuildStateFromTemplateCell(Sheet.Cells.Item[1, 1]);
  // Форматира колони B..D, без да създава нито един обект за празна клетка
  Sheet.StyleOverlays.Add(1, 2, MaxRowIndex, 4,
    [xfpNumberFormat, xfpAlignment], State);
end;

TXLSXFormatParts е множеството, което решава какво допринася едно наслагване: xfpFont, xfpFill, xfpBorder, xfpNumberFormat, xfpAlignment и xfpProtection. Назоваването само на частите, които имате предвид, е това, което позволява на наслагванията да се слояват разумно — наслагване на колона, което доставя числов формат, не се бори с наслагване на ред, което доставя запълване, защото нито едното претендира за частта на другото

Какво ви дава блокът от 256 реда

Локалност. Клетките се държат в блокове от 256 реда със стабилни публични манипулатори, сериализирани редово-мажоритарно, така че писането на работен лист върви в паметта в реда, в който ще излъчва байтове, вместо да преследва указатели из хийпа. Стабилните манипулатори имат значение за API повърхността: манипулатор, който извикващият държи, остава валиден през вътрешната реорганизация, която блоковото оформление извършва, което е точно това, което прави компактното представяне детайл на реализацията, а не счупваща промяна

Диаграма на съхранение в блокове от редове, показваща клетки на HotXLS лист, държани в фиксирани блокове от 256 реда със стабилни Delphi манипулатори и сериализация по редове в XLSX deflate потока
Клетките седят в фиксирани блокове от 256 реда, които заделят само заети редове и се сериализират в същия ред, в който байтовете напускат процеса. Публичните handle-и оцеляват през вътрешна реорганизация, затова блоковата подредба остава невидима за извикващите

Компактирането на пула от стилове тече успоредно с него. Преди всеки запис шрифтове, запълвания, рамки, числови формати, подравнявания и защити, които никоя клетка не реферира, биват отхвърляни. Работни книги с дълъг живот натрупват нередерирани записи на стилове по начина, по който дългоживеещи документи натрупват неизползвани стилове, а работна книга, която е била редактирана от потребител час, може да носи стотици от тях във файл, от който никой никога няма да ги прочете

Записване по редове и кога не се прилага

С StreamingWrite включено — по подразбиране — всеки ред на работен лист се записва директно в deflate потока на пакета. Алтернативата, която е това, което флагът изключва, изгражда пълното работно лист XML първо и го компресира след това, така че пиковата памет мащабира с целия лист. Струйното прави я мащабира с един ред

Споделените низове и помощните части следват същата дисциплина чрез един преизползваем UTF-8 сериализатор, който излъчва записи един по един, ограничавайки пиковата памет по най-големия единичен запис, а не по цялата част. Това покрива таблицата със споделени низове и pivot записи, които при широка аналитична работна книга често са по-големи от всеки отделен работен лист

var
  Workbook: TXLSXWorkbook;
begin
  Workbook := TXLSXWorkbook.Create(nil);
  try
    Workbook.Open('ledger-2026.xlsx');
    // StreamingWrite по подразбиране е True; изключвайте го само когато следваща
    // стъпка изисква целия XML на работния лист да съществува преди компресията
    Workbook.StreamingWrite := True;
    Workbook.SaveAs('ledger-2026-out.xlsx');
  finally
    Workbook.Free;
  end;
end;

Оставете го включен, освен ако нямате конкретна причина да не го направите. Неструйният път съществува за случаите, когато нещо друго в пайплайна се нуждае от сглобеното XML, а заплащането за него по подразбиране е плащане за случай, който повечето приложения никога не удрят

Как да разберете дали наслагванията реално се използват

Следете броя клетки, а не графиката на паметта. Ако работен лист отчита правдоподобен брой физически клетки, след като сте приложили широко форматиране, наслагванията вършат работата си. Ако броят скача с размера на форматирания диапазон, нещо в кодовия път е материализирало клетките — обикновено цикъл, който чете всяка клетка в диапазона, за да провери стила си, което принуждава разрешаване една клетка по едно време и проваля цялата подредба

Диаграма на StreamingWrite, сравняваща ред-по-ред поточно записване с deflate в HotXLS срещу сглобяване на целия XML на листа в паметта преди компресия в Delphi
С включен StreamingWrite всеки ред тече направо в deflate потока на пакета, а пикова памет следи един ред. Изключването на флага първо сглобява целия worksheet XML, което мащабира пикова памет с целия лист

Разрешете стил, когато се нуждаете от ефективен формат на една клетка. Не разрешавайте стил за милион клетки, за да разберете, че колоната има числов формат; попитайте наслагването. Същото правило се прилага за писане: присвоявайте стойности на клетките, които имат стойности, и оставете форматирането да остане интервал

Къде отива останалата памет

След като клетките и стиловете са компактни, следващите най-големи консуматори в голяма работна книга са таблицата със споделени низове и каквито сателитни части файлът носи — pivot кешове, чертежи, запазено XML от части, които обектният модел не моделира. Тези имат свои собствени стратегии, а честният отговор е, че нито една настройка решава всички наведнъж

Ако вашето тясно място е отварянето, а не записът, селективното зареждане е лостът: ръководството за зареждане само на метаданни и селективно на работни листове разглежда четенето на работна книга, без да плащате за листовете, които няма да докоснете. За производителност на пътя на четене при много големи файлове вижте записките за паралелен XLSX анализ и алокаторът на памет, а за изходни натоварвания, които изобщо не се нуждаят от обектен модел, струйно записване за партидни задачи на сървър обикновено пасва по-добре от какъвто и да е тунинг тук

HotXLS чете и записва XLS и XLSX от нативен Delphi и C++Builder код без инсталиран Excel и без OLE автоматизация, което е точно това, което прави тези характеристики на паметта наблюдаеми и контролируеми на първо място — страницата на HotXLS компонент за електронни таблици изброява поддържаните формати и версии на RAD Studio