Artykuł techniczny

Pamięć komórek w blokach wierszy i strumieniowy zapis XLSX w HotXLS

HotXLS przechowuje komórki arkusza w kompaktowych blokach po 256 wierszy, rozwiązuje formatowanie wierszy, kolumn i prostokątów przez leniwe nakładki interwałowe zamiast tworzyć obiekty komórek i strumieniuje każdy wiersz prosto do strumienia deflate paczki przy zapisie. Razem te trzy zmiany decydują o profilu pamięci dużego skoroszytu: szczytowe użycie idzie za największym pojedynczym wierszem zamiast za rozmiarem kompletnego XML arkusza

Powód, dla którego to ma znaczenie, to kształt, z którym każdy programista arkuszy kalkulacyjnych spotyka się w końcu. Użytkownik formatuje całą kolumnę — jedno kliknięcie, milion komórek — a naiwny model obiektów odpowiada alokacją miliona obiektów komórek, by trzymać jeden indeks formatu liczbowego. Plik na dysku zostaje malutki, bo format XLSX wyraża to jako jeden wpis <col>. Proces wcale nie zostaje malutki

Dlaczego formatowanie kolumny kosztuje więcej pamięci niż jej wypełnienie?

Ponieważ formatowanie nie ma danych, które usprawiedliwiałyby obiekt. Komórka z wartością musi gdzieś istnieć. Komórka pusta, ale stylizowana, istnieje tylko po to, by nieść indeks stylu, a materializacja milionów takich to klasyczny sposób, w jaki aplikacja arkuszowa w Delphi kończy przestrzeń adresową na pliku, który Excel otwiera natychmiast

Nakładki stylu interwałowego usuwają tę potrzebę. Instrukcja formatowania wiersza, kolumny lub prostokąta jest zapisywana raz jako zakres plus części stylu, które wnosi, i rozwiązuje się leniwie, gdy komórka w tym zakresie jest faktycznie dostępna. Nakładki przeżywają edycje strukturalne — wstawienie wiersza wewnątrz sformatowanego bloku przesuwa interwał zamiast go przebudowywać — i podróżują w obie strony jako kompaktowe wpisy kolumn, wierszy i komórek samym stylem, co jest dokładnie tym, jak zapisuje je Excel

var
  Sheet: TXLSXWorksheet;
  State: TXLSXCellStyleState;
begin
  Sheet := Workbook.Sheets[1];
  // Style indexes come from the workbook style pools, e.g. from a cell
  // you have already formatted the way you want the range to look
  State := BuildStateFromTemplateCell(Sheet.Cells.Item[1, 1]);
  // Format columns B..D without creating a single empty cell object
  Sheet.StyleOverlays.Add(1, 2, MaxRowIndex, 4,
    [xfpNumberFormat, xfpAlignment], State);
end;

TXLSXFormatParts to zbiór, który decyduje, co wnosi nakładka: xfpFont, xfpFill, xfpBorder, xfpNumberFormat, xfpAlignment i xfpProtection. Nazwanie tylko części, o które chodzi, jest tym, co pozwala nakładkom układać się rozsądnie — nakładka kolumny dostarczająca format liczbowy nie walczy z nakładką wiersza dostarczającą wypełnienie, bo żadna nie rości sobie części drugiej

Co daje blok 256-wierszowy

Lokalność. Komórki są trzymane w blokach po 256 wierszy ze stabilnymi uchwytami publicznymi, serializowane w porządku wiersz-główny, więc zapis arkusza idzie po pamięci w kolejności, w jakiej emituje bajty, zamiast gonić wskaźniki po stercie. Stabilne uchwyty mają znaczenie dla powierzchni API: uchwyt, który trzyma wywołujący, zostaje poprawny przez reorganizację wewnętrzną, jaką wykonuje układ bloków, co czyni kompaktową reprezentację detalem implementacyjnym zamiast zmianą łamiącą

Kompaktacja puli stylów biegnie obok. Przed każdym zapisem są porzucane czcionki, wypełnienia, obramowania, formaty liczbowe, wyrównania i ochrony, których nie referencjuje żadna komórka. Długowieczne skoroszyty gromadzą niereferencjonowane rekordy stylu tak, jak długowieczne dokumenty gromadzą nieużywane style, a skoroszyt edytowany przez użytkownika przez godzinę może nieść ich setki do pliku, z którego nikt nigdy ich nie odczyta

Zapis strumieniowany wierszami i kiedy nie ma zastosowania

Przy StreamingWrite włączonym — domyślnie — każdy wiersz arkusza jest zapisywany bezpośrednio do strumienia deflate paczki. Alternatywa, którą flaga wyłącza, buduje najpierw kompletny XML arkusza i kompresuje go potem, więc szczyt pamięci skaluje się z całym arkuszem. Strumieniowanie sprawia, że skaluje się z jednym wierszem

Współdzielone ciągi znaków i części pomocnicze idą za tą samą dyscypliną przez jeden wielokrotnie używany serializator UTF-8, który emituje wpisy jeden naraz, ograniczając szczyt pamięci przez największy pojedynczy wpis zamiast przez całą część. To obejmuje tabelę ciągów współdzielonych i rekordy pivot, które na szerokim skoroszycie analitycznym bywają częściej większe niż jakikolwiek pojedynczy arkusz

var
  Workbook: TXLSXWorkbook;
begin
  Workbook := TXLSXWorkbook.Create(nil);
  try
    Workbook.Open('ledger-2026.xlsx');
    // StreamingWrite defaults to True; turn it off only when a downstream
    // step requires the whole worksheet XML to exist before compression
    Workbook.StreamingWrite := True;
    Workbook.SaveAs('ledger-2026-out.xlsx');
  finally
    Workbook.Free;
  end;
end;

Zostaw to włączone, chyba że masz konkretny powód, by nie. Ścieżka niestumieniowa istnieje dla przypadków, gdzie coś innego w potoku potrzebuje złożonego XML, a płacenie za to domyślnie to płacenie za przypadek, w który większość aplikacji nigdy nie wpada

Jak poznać, czy nakładki faktycznie są używane

Obserwuj liczbę komórek, a nie wykres pamięci. Jeśli arkusz raportuje wiarygodną liczbę fizycznych komórek po nałożeniu szerokiego formatowania, nakładki robią swoją robotę. Jeśli licznik skacze o rozmiar sformatowanego zakresu, coś w ścieżce kodu zmaterializowało komórki — zazwyczaj pętla czytająca każdą komórkę w zakresie, by sprawdzić jej styl, co wymusza rozwiązywanie jednej komórki naraz i pokonuje całe założenie

Rozwiąż styl, gdy potrzebujesz efektywnego formatu jednej komórki. Nie rozwiązuj stylu dla miliona komórek, żeby dowiedzieć się, że kolumna ma format liczbowy; zapytaj nakładkę. Ta sama reguła dotyczy pisania: przypisz wartości komórkom, które mają wartości, a pozwól formatowaniu zostać interwałem

Gdzie idzie reszta pamięci

Gdy komórki i style są kompaktowe, najwięksi kolejni konsumenci w dużym skoroszycie to tabela ciągów współdzielonych i cokolwiek satelitarnych części niesie plik — pamięci podręczne pivot, rysunki, zachowany XML z części, których model obiektów nie modeluje. Te mają własne strategie, a uczciwa odpowiedź brzmi, że żadne pojedyncze ustawienie nie rozwiązuje wszystkich na raz

Jeśli wąskim gardłem jest otwieranie zamiast zapis, selektywne ładowanie jest dźwignią: przewodnik po ładowaniu tylko metadanych i selektywnym ładowaniu arkuszy obejmuje czytanie skoroszytu bez płacenia za arkusze, których nie dotkniesz. Dla przepustowości ścieżki odczytu przy bardzo dużych plikach zobacz notatki o równoległym parsowaniu XLSX i alokatorze pamięci, a dla obciążeń czysto wyjściowych, które nigdy nie potrzebują modelu obiektów wcale, zapis strumieniowy dla zadań wsadowych serwera jest zwykle lepszym dopasowaniem niż jakiekolwiek strojenie tutaj

HotXLS czyta i zapisuje XLS i XLSX z natywnego kodu Delphi i C++Builder bez instalacji Excela i bez automatyzacji OLE, co właśnie czyni te charakterystyki pamięci obserwowalnymi i kontrolowalnymi w ogóle — strona komponentu arkuszowego HotXLS wymienia obsługiwane formaty i wersje RAD Studio