Teknik Makale

HotXLS ile Satır Bloğu Hücre Depolama ve Akışlı XLSX

SpreadsheetML'de para birimi biçimi taşıyan bir sütun tam olarak tek bir öğe ister: <col min="2" max="2" style="3"/>. ECMA-376 Bölüm 1 bir hücre stilini yalnızca üç yerde bağlar: bir sütun (§18.3.1.13), bir satır (§18.3.1.73) ve bir hücre (§18.3.1.4). Biçimi B2:D500000'e daraltın; SpreadsheetML'in o şekil için tek bir öğesi yoktur: bir sütun stili 500000. satırı aşardı, bir satır stili her sütunu kapsardı; sonuçta bir yazıcı, biçimi yalnızca stil içeren hücreler olarak — 1.499.997 tanesini — yayımlar

Bellek içi bir elektronik tablo modeli aynı duvara bir adım önce çarpar. Biçimlendirme, her hücre nesnesine özellik atamak demekse sütun başlığına tek bir tık, tek bir küçük tamsayıyı taşımak için bir milyon nesne ayırır ve yalnızca bir sayı biçimi isteyen bir Delphi süreci, diskte birkaç kilobaytlık bir çalışma kitabında adres alanını tüketir. Gerçek tasarım sorusu şudur: biçimlendirme düzenleme sırasında nerede yaşar ve ne zaman hücre başına veriye dönüşür

HotXLS bir aralığı hücre oluşturmadan nasıl biçimlendirir

HotXLS aralık biçimlendirmesini aralıklar (intervals) olarak tutar. TXLSXRange üzerindeki her stil ayarlayıcı — SetNumberFormat, SetNumberFormatIndex, SetFontIndex, SetFillIndex, SetBorderIndex, SetAlignmentIndex — çalışma sayfasının StyleOverlays listesine bir kayıt ekler: dört koordinat, çağrının sahiplendiği stil parçaları ve o parçaların havuz indeksleri. Dikdörtgenin içinde hâlihazırda var olan hücreler yeni parçaları anında alır. Boş koordinatlar boş kalır ve onlar için hiçbir şey ayrılmaz

var
  Workbook: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Edge: TXLSXBorder;
  BorderIdx, Row: Integer;
  Stats: TXLSXCellStorageStatistics;
begin
  Workbook := TXLSXWorkbook.Create;
  try
    Sheet := Workbook.Sheets.Add('Ledger');
    Sheet.WriteStringRowValues(1, 2, ['Net', 'Tax', 'Gross']);
    for Row := 2 to 1001 do
      Sheet.WriteFloatRowValues(Row, 2, [Row * 1.5, Row * 0.3, Row * 1.8]);

    // 1.048.576 satırın hepsinde B..D sütunları: çağrı başına bir aralık
    Sheet.RCRange[1, 2, XlsxMaxRow, 4].SetNumberFormat('#,##0.00 "EUR"');

    // Kenarlığı bir kez kurun, sonra indeksle uygulayın
    Edge := TXLSXBorder.Create;
    try
      Edge.SetAll(xlsxBorderThin);
      BorderIdx := Workbook.Borders.LookupOrAdd(Edge); // 1 tabanlı
    finally
      Edge.Free;
    end;
    Sheet.RCRange[1, 2, XlsxMaxRow, 4].SetBorderIndex(BorderIdx);

    Stats := Sheet.Cells.GetStorageStatistics;
    // 3.003 bekleyin: üç başlık hücresi artı 3.000 değer
    WriteLn('Cells: ', Stats.CellCount);
    if Workbook.SaveAs('ledger.xlsx') <> 1 then
      raise Exception.Create('Save failed');
  finally
    Workbook.Free;
  end;
end;

İki biçimlendirme çağrısı 3.145.728 koordinatı kapsadı ve o sayıma hiçbir şey eklemedi. Aralıklar bilerek 1. satırdan başlar: tam sütunlu bir aralık tek bir <col> öğesi olarak kaydedilir ve 1. satırdaki metin başlıklar zaten bir sayı biçiminden etkilenmez. RCRange, Range['A1:C5'], EntireColumn ve EntireRow'un döndürdüğü aralıklar çalışma sayfasının mülkiyetindedir; örnek onları hiç serbest bırakmaz

TXLSXFormatParts, birkaç aralığın çekişmesini önleyen şeydir. Üyeleri xfpFont, xfpFill, xfpBorder, xfpNumberFormat, xfpAlignment, xfpProtection ve xfpCellControl'dur ve her TXLSXRange ayarlayıcısı tam olarak birini sahiplenir. Bir hücre çözümlendiğinde onu kapsayan aralıklar eklenme sırasına göre uygulanır; her biri yalnızca kendi parçalarını yazar ve hücrenin üzerindeki açık biçimlendirme hepsini geçer. Sayı biçimini ayarlayan bir sütun aralığı ile dolgu ayarlayan bir satır aralığı, ikisi de B5 hücresine sağlam ulaşır

Tek bir kaydın birkaç parça taşıması gerektiğinde Sheet.StyleOverlays.Add(Row1, Col1, Row2, Col2, Parts, State), havuz indeksleri doldurulmuş bir TXLSXCellStyleState alır. Add koordinatları 1.048.576 × 16.384 örgüsüne kırpıp ters çevrilmiş köşeleri düzeltir ve Parts boşken hiçbir şey kaydetmeden nil döndürür. Yapısal düzenlemeler kayıtları adımında tutar: biçimlendirilmiş bir bloğun içindeki InsertRows aralığı eklenen sayı kadar genişletir ve üstüne ekleme bütün kaydı aşağı kaydırır

HotXLS için aralık stili katmanı şeması: Delphi'de bir milyon boş hücre nesnesi yerine bir sütun biçimlendirme aralığının, talep edilmiş biçim parçalarıyla tek bir kayıt olarak depolanmasını gösterir
Sütun, satır ya da dikdörtgen biçimi, yalnızca sahip olduğu kısımları talep eden bir aralık katmanı kaydı olarak bir kez yaşar. Etkin stil, aralık içindeki bir hücreye gerçekten dokunulan ilk seferde tembelce kurulur

SetBorders neden hücre oluşturur da SetBorderIndex oluşturmaz?

TXLSXRange.SetBorders(Kind, Style, Color) her hücreye tek tek bakmak zorundadır, çünkü yanıt konuma bağlıdır. xlsxEdgeOutline ile üst satır üst kenarlığı alır, sol sütun sol kenarlığı alır, iç kısım hiçbir şey almaz; xlsxEdgeInside ile durum kabaca terstir. Çağrı ayrıca yeni kenarları, hücrenin hâlihazırda sahip olduğu kenarlıkla birleştirir; önce Outline sonra Inside, dolu bir örgü kurar. O hücre başına bileşim, var olmayan bir hücreyi oluşturan Cells[R, C] üzerinden geçer. 10 × 40'lık bir rapor bloğunda bu tam olarak doğrudur. xlsxEdgeAll ile EntireColumn(3)'te ise 1.048.576 yeni hücre nesnesidir

Böylece bölünme basittir. Bir ekran verisinden uzun her şey için kenarlığı ilk örneğin yaptığı gibi bir kez kurun ve indeksle uygulayın: TXLSXBorder.SetAll, sonra 1 tabanlı indeks için Workbook.Borders.LookupOrAdd, sonra SetBorderIndex. SetBorders'i, dış çizgisiyle iç çizgileri farklılaşan bloklar için saklayın; yukarıdaki 10 × 40 rapor bloğu gibi

İşi geri almadan biçimleri geri okumak

Aynı tuzak yalnızca okuyan kodu da yakalar. Cells[R, C], isabet olmayınca oluşturan bir erişicidir: B500000'in sayı biçimini sorun, doğru yanıtı artı B500000'de artık saklanacak ve kaydedilecek biçimli, boş yeni bir hücre alırsınız. Bunu biçimlendirmeyi denetlemek için bir sütun boyunca döndürün; aralığın önlemek için var olduğu milyon nesneyi, görüntüsü masum tek tek okumayla yeniden kurmuş olursunuz

TXLSXWorksheet.GetCellVisibleFormat, bir koordinatın etkin stilini hücre eklemeden çözer. Çalışma sayfası öntanımlısını, satır ve sütun aralıklarını, dikdörtgen aralıklarını, tablo stillerini ve hücre üzerindeki açık biçimlendirmeyi birleştirir. out ABuiltinNumberFormat parametreli aşırı yükleme, 14 (kısa tarih) gibi gömülü bir Excel biçimini, çalışma kitabının özel NumberFormats havuzuna işaret eden NumberFormatIndex'ten ayrı olarak bildirir. Cells.HasCell(R, C), fiziksel bir hücrenin var olup olmadığını yanıtlar

var
  Sheet: TXLSXWorksheet;
  Before, After: TXLSXCellStorageStatistics;
  State: TXLSXCellStyleState;
  Row, Builtin, Unformatted, Physical: Integer;
begin
  Sheet := Workbook.Sheets[1];  // Sheets 1 tabanlıdır
  Before := Sheet.Cells.GetStorageStatistics;
  Unformatted := 0;
  Physical := 0;
  for Row := 1 to 200000 do
  begin
    // Etkin stil, sayfaya hücre eklemeden çözümlenir
    State := Sheet.GetCellVisibleFormat(Row, 2, Builtin);
    if (State.NumberFormatIndex = 0) and (Builtin = 0) then
      Inc(Unformatted);
    if Sheet.Cells.HasCell(Row, 2) then
      Inc(Physical);
    // Sheet.Cells[Row, 2].NumberFormatIndex aynı soruyu yanıtlar,
    // ama ziyaret ettiği her satır için arkasında bir yeni hücre nesnesi bırakır
  end;
  After := Sheet.Cells.GetStorageStatistics;
  // İki sayım da döngüden önce ve sonra aynı olmalı
  WriteLn(Format('cells %d -> %d, styled blanks %d -> %d',
    [Before.CellCount, After.CellCount,
     Before.StyledBlankCellCount, After.StyledBlankCellCount]));
end;

Böyle bir denetimi gerçek iş yükünüze karşı bir kez çalıştırın. StyledBlankCellCount, stili olan ama değeri, formülü ya da zengin metni olmayan hücreleri sayar. Salt okunur olması gereken bir geçiş sırasında tırmanıyorsa bir yardımcı, salt stil taşımak için hücre oluşturuyor demektir; tipik olarak bir hücrenin biçimlendirilip biçimlendirilmediğine karar vermek için Cells[R, C] çağırıyor

256 satırlık blokların kodunuz için anlamı

Fiziksel hücreler 256 satırlık bloklarda yaşar. Bloğun içinde her dolu satır hücrelerini sütuna göre sıralı bir listede tutar; hücresiz bir satır yalnızca boş bir yuva, hücresiz bir 256'lık dilimin ise hiç bloğu yoktur. Verisi 3, 7 ve 512. satırlarda olan seyrek bir sayfa bu yüzden iki blok tutar. Kaydetme aynı yapıyı satır öncelikli sırada gezer; SpreadsheetML'in <row> öğeleri ile <c> çocukları için istediği sıra budur

Satır bloğu depolama şeması: HotXLS çalışma sayfası hücrelerinin kararlı Delphi tanıtıcılarıyla sabit 256 satırlık bloklarda tutulmasını ve satır-başına serileştirmeyle XLSX deflate akışına yazılmasını gösterir
Hücreler, yalnızca işgal edilmiş satırları ayıran ve baytlar süreçten çıkarken aynı sırada serileşen sabit 256 satırlık bloklarda durur. Genel tanıtıcılar içsel yeniden düzenlemeyi sağ çıkarır; böylece blok düzeni çağıranlara görünmez kalır

Tuttuğunuz TXLSXCell referansı, çevresine başka hücreler eklenirken ya da çıkarılırken aynı nesne olarak kalır; bloklar kendi iç kayıtlarını yeniden düzenler, sizin tanıtıcılarınızı değil. WriteFloatRowValues ile WriteStringRowValues gibi toplu yazıcılar, taze bir sayfada daha hızlı yolu seçer ve satır öncelikli değerleri paketlenmiş bir depoya ekler. Cells[R, C] üzerinden yapılan ilk hücre başına erişim o depoyu bir kez hücre nesnelerine çevirir. GetStorageStatistics, hangi durumda olduğunuzu PackedStorageActive ve PackedCellCount ile, yanında RowBlockCount ve PopulatedRowCount ile gösterir

Stil havuzları kaydetme anında kırpılır. styles.xml yazılmadan önce HotXLS, yazı tipi, dolgu, kenarlık, sayı biçimi, hizalama ve koruma havuzlarını yalnızca hâlâ kullanımda olan girdileri tutacak biçimde yeniden kurar; bir kullanıcının bir saat boyunca yeniden stillendirdiği bir çalışma kitabı, terk edilmiş her dolguyu taşımaz. Mutlak bir FormatIndex'e sabitlenmiş hücreler yuvalarını olduğu gibi korur

Satır akışlı kayıtlar ve bunu gösteren sayılar

TXLSXWorkbook.StreamingWrite öntanımlı olarak True'dur. Açıkkken SaveAs, her çalışma sayfası <row>'unu üretir ve doğrudan ZIP girdisinin deflate akışına yazar; böylece bellekteki en büyük çalışma sayfası XML parçası her an tek bir satırdır. Kapalıyken her çalışma sayfasının XML'i önce tek bir eksiksiz dize olarak kurulur ve sonra sıkıştırılır; tepe bellek en büyük sayfayı izler. Dikdörtgen aralıklarının dönüştürdüğü yalnızca stil içeren hücreler — açılıştaki B2:D500000 durumu — o satır başına döngünün içinde üretilir; dosyaya eklenirler ama bellekte tek bir XML bloğu olarak asla durmazlar

Paylaşılan dizeler ile özet önbellek kayıtları aynı deseni izler. Her <si> ya da <r> girdisi kendi başına üretilir ve yazılır; böylece geniş bir analitik çalışma kitabında paylaşılan dize tablosu — çoğunlukla herhangi bir tekil çalışma sayfasından büyük — tek bir XML bloğu olarak var olmak zorunda kalmaz

StreamingWrite şeması: HotXLS'de satır başına deflate akışını, Delphi'de sıkıştırmadan önce tüm çalışma sayfası XML'sini bellekte birleştirmekle karşılaştırır
StreamingWrite açıkken her satır paketin deflate akışına doğrudan akar ve tepe bellek tek bir satırı izler. Bayrağı kapatmak önce çalışma sayfası XML'inin tamamını kurar; bu da tepe belleği bütün sayfayla ölçeklendirir

Bunların hiçbirine körü körüne güvenmek gerekmez. Her kayıttan sonra çalışma kitabı dört salt okunur özellik açar: LastSaveUsedStreaming, LastSavePeakRowXmlChars (en uzun tek satır, karakter olarak), LastSaveBufferedWorksheetXmlChars (bellekte tutulan toplam çalışma sayfası XML'i, akışta sıfır) ve LastSavePeakAuxiliaryXmlChars (en uzun paylaşılan dize ya da özet girdisi). LastSaveUsedStreaming, .xlsx, .xltx ve .xltm çıktısında ve ikili .xlsb yazıcısında True'dur; OpenDocument çıktısı kendi yazıcısından geçer ve False bildirir

procedure ReportSave(Workbook: TXLSXWorkbook; const FileName: string;
  Streaming: Boolean);
begin
  Workbook.StreamingWrite := Streaming;
  if Workbook.SaveAs(FileName) <> 1 then
    raise Exception.CreateFmt('Save failed: %s', [FileName]);
  WriteLn(Format('streaming=%s peakRow=%d buffered=%d peakAux=%d',
    [BoolToStr(Workbook.LastSaveUsedStreaming, True),
     Workbook.LastSavePeakRowXmlChars,
     Workbook.LastSaveBufferedWorksheetXmlChars,
     Workbook.LastSavePeakAuxiliaryXmlChars]));
end;

// Aynı çalışma kitabı, iki mod; süreç belleğini profiler'ınızda izleyin
ReportSave(Workbook, 'ledger-stream.xlsx', True);
ReportSave(Workbook, 'ledger-buffered.xlsx', False);

Kendi verinizde tamponlu satır, bütün sayfanın XML'i mertebesinde bir buffered değeri gösterirken akışlı satır sıfır ve en geniş satırınızdan büyük olmayan bir peakRow gösterir. StreamingWrite'i açık bırakın. Bayrak, yalnızca çalışma sayfası XML'inin sıkıştırmadan önce eksiksiz kurulup kurulmayacağına karar verir ve LastSaveBufferedWorksheetXmlChars o kurulumun boyutunu karakter olarak bildirir

Geri kalan bellek nereye gider?

Hücreler ve stiller sıkışık hâle geldikten sonra büyük bir çalışma kitabındaki sıradaki en büyük tüketiciler paylaşılan dize tablosu ile dosyanın taşıdığı uydu parçalardır: özet önbellekleri, çizimler ve nesne modelinin modellemediği parçalardan korunan XML. Bunların kendi kolları vardır ve tek bir ayar hepsini birden kapsamaz

Darboğazınız kaydetmek yerine açmaysa, seçmeli yükleme koldur: yalnızca üst veri ve seçmeli çalışma sayfası yükleme yazısı, bir çalışma kitabını dokunmayacağınız sayfalar için ödemeden okumayı ele alır. Çok büyük dosyalarda okuma yolu verimliliği için paralel XLSX ayrıştırma ve bellek ayırıcı notlarına bakın ve hiç nesne modeline ihtiyaç duymayan yalnızca çıktı iş yükleri için sunucu toplu işleri için akışlı yazma çoğunlukla buradaki herhangi bir miktar ayarlamadan daha iyi bir uyumdur

HotXLS, XLS ve XLSX'i Excel ya da OLE otomasyonu olmaksızın yerel Delphi ve C++Builder kodundan okur ve yazar; yukarıdaki her sayı kendi sürecinizden gelir ve orada denetlenebilir. Desteklenen biçimler ile RAD Studio sürümleri HotXLS Delphi elektronik tablo bileşeni sayfasında listelenir