Techninis straipsnis

Pull row kursorius XLS, XLSX, ODS ir CSV Delphi

HotXLS skaito .xls, .xlsx, .xlsm, .ods, CSV ir TSV šaltinius per vieną pull row kursorių TXLSRowCursor, kurio FindFirst ir FindNext žengia po vieną loginę eilutę, o atmintyje lieka tik ta eilutė. Šešių reikšmių būsenų mašina atskiria before-first nuo EOF, atšaukto ir sugedusio, o senesnis callback skaitytuvas dabar yra adapteris ant to paties kursoriaus

Scenarijus pažįstamas kiekvienam, kas yra išleidęs importo funkciją. Atkeliauja 200 MB .xlsx, prijungiate OnCell handlerį, o pirmasis reikalavimas po „perskaityk“ yra „sustok po pirmų šimto atšauktų įrašų“. Dabar jūsų kodo forma kovoja prieš jus: ciklas gyvena bibliotekos viduje, jūsų handleris turi pakelti flagą, visi tolesni callbackai vis tiek suveikia, kol analizatorius pastebi, ir sukaupta būsena — kiek atitikmenų iki šiol, kuris stulpelis atitiko, ką daryti toliau — turi gyventi laukuose klasės, kuri egzistuoja tik tam, kad callbackas turėtų kur sėdėti. Nieko apie tai nėra analizavimo problema. Tai valdymo srauto problema, ir būtent ją pašalina pull kursorius

Ką push callback iš tikrųjų kainuoja ties 200 MB

Push apverčia valdymą, o apvertimas yra būtent tai, ko filtruojantis ar jungiantis kvietėjas negali sau leisti. Su callback API biblioteka valdo ciklą, tad kvietėjas negali naudoti Break, negali perpinti dviejų šaltinių, negali perduoti skaitytuvo rutinai, kuri tikisi būti valdoma, ir negali išreikšti „pažiūrėk į kitą eilutę prieš nuspręsdamas“ be buferiavimo. Kaina nėra pralaidumas — gerai parašytas SAX callback kelias srautuoja gerai — tai, kad kiekvienas netrivialus vartotojas užaugina mažą savo būsenų mašiną, imituoti ciklą, kurio jam neleista rašyti. Padauginkite tai iš keturių failo formatų, kiekvienas istoriškai su savu skenavimo įėjimo tašku, ir filtravimo, formulės bei klaidų semantikos pradeda dreifuoti viena nuo kitos — būtent tas dreifas, kurį HotXLS ėjosi uždaryti

Kaip pull kursorius keičia jūsų kviečiantį kodą?

Jis grąžina ciklą jums, o kartu ir paprastą Pascal valdymo srautą. TXLSRowCursor.Open priima failo vardą arba TStream, atpažįsta formatą, kartą įkelia shared strings ir datos stiliaus metaduomenis ir pasirenka 1 lapą. SelectSheet (nuo vieneto) arba SelectSheetByName perjungia į kitą darbalapį ir atstato kursorių į before-first. FindFirst ir FindNext tada pozicionuoja ant kitos apgyvendintos eilutės — eilutės be dekoduojamų langelių praleidžiamos, tad RowIndex gali šuoliauti — o dabartinė eilutė atskleista kaip CellCount, Cells[] ir ValueByCol[], visi nuo vieneto stulpelių ašyje. Išėjimas iš ciklo yra Break

var
  Cursor: TXLSRowCursor;
  Hits: Integer;
begin
  Cursor := TXLSRowCursor.Create;
  try
    Cursor.FirstRow := 2;        // praleisti antraštės juostą
    Cursor.IncludeColumn(1);     // dekoduoti tik šiuos du stulpelius
    Cursor.IncludeColumn(7);
    if not Cursor.Open('postings-200mb.xlsx') then
      Exit;
    if not Cursor.SelectSheetByName('Ledger') then
      Exit;

    Hits := 0;
    if Cursor.FindFirst then
      repeat
        if VarToStr(Cursor.ValueByCol[7]) = 'REVERSED' then
        begin
          Inc(Hits);
          if Hits = 100 then
            Break;               // paprastas Break; jokio abort flago, jokio sargo
        end;
      until not Cursor.FindNext;
  finally
    Cursor.Free;                 // destruktorius baigia perėjimą
  end;
end;

Projekcija ir diapazonas nustatomi prieš perėjimą, o ne filtruojami po to. FirstRow, LastRow, IncludeColumn, ClearColumnProjection, IncludeFormulaText, DetectDates ir DetectTextTypes visi gerbiami backendų viduje, tad nepasirinktas stulpelis niekada apskritai nealokuoja savo reikšmės, formulės eilutės ar rich-text naštos — regresijos rinkinys tai įrodo su 16 KiB formulėmis ir talpyklinėmis eilutėmis, kurios niekada neįgyvendinamos, kai jų stulpelis nėra projektuotas. Tos parinktys tyčia užšaldytos, kol perėjimas aktyvus, ir vėl tampa rašomomis ties EOF, ant SelectSheet arba po Close, tad vienas skenavimas niekada negali sumaišyti dviejų dekodavimo kontraktų. Jei jums reikia tik lapų inventoriaus, o ne eilučių, tik metaduomenims ir selektyvus lapų įkėlimas yra pigesnis įėjimo taškas

Vienas backendas per formatą, po vieną skenavimo ciklą kiekvienam

Kiekvienas formatas turi tiksliai vieną forward skaitytuvą HotXLS viduje, ir tiek pull kursorius, tiek callback skaitytuvas valdo tą patį skaitytuvą. TXLSXForwardRowBackend yra vienintelė darbalapio SAX būsenų mašina ECMA-376 Part 1 §18.3 lapų dalims, laikanti XML skaitytuvą, bendrų formulių lentelę ir rich-text analizatorių, ir ji žengia iki tiksliai vienos fizinės <row> ribos per iškvietimą. TXLSBiffForwardParser valdo globals, lapo pasirinkimą ir eilutės žingsnį [MS-XLS] įrašų srautui; jo padarymas pristabdomu pagimdė aštriausią viso dizaino apribojimą, nes talpyklinė eilutės formulė yra Formula įrašas, iškart sekamas String įrašo, tad per eilutę skirta sustojimo vieta niekada neturi nukristi tarp jų dviejų. TXLSForwardTextBackend laiko BOM žinantį skaitytuvą, aktyvųjį skiriklį ir vieną loginį įrašą — CSV uosto komą, kabliataškį, tabuliatorių ar vamzdį iš pirmo įrašo, ignoruodamas cituotus simbolius, o daug eilučių cituoti laukai sujungiami su #10, tad eilutės numeris seka loginius įrašus, o ne fizinius naujos eilutės simbolius. TXLSForwardOdsBackend laiko vieną fizinės eilutės šabloną OpenDocument §9 lentelėms, traktuoja table:number-rows-repeated kaip likusių skaičių, o ne išskleidimą, ir žengia pro padengtus langelius neišduodamas reikšmių. Srautinis tiesioginis skaitytuvas dalijasi tuo pačiu shared-string ir datos stiliaus įkėlėju

HotXLS pull row kursorius, maršrutizuojantis į po vieną forward skaitytuvą per formatą: SAX backendas XLSX, įrašų analizatorius BIFF, skiriklį uostantis tekstų backendas ir ODS eilutės šablonas, o callback skaitytuvas sukonfigūruotas viršuje kaip adapteris
Kiekvienas formatas turi tiksliai vieną forward skaitytuvą, ir tiek pull kursorius, tiek callback skaitytuvas valdo tą patį skaitytuvą, tad filtravimo ir klaidų semantika negali dreifuoti viena nuo kitos

Kodėl šešios būsenos vietoj vieno Eof flago?

Nes vienas booleanas padaro keturias skirtingas situacijas neatskiriamomis, o kvietėjai apie visas spėja klaidingai. TXLSRowCursorState jas įvardija aiškiai

  • xrcsClosed — joks šaltinis neatidarytas
  • xrcsBeforeFirst — atidarytas arba perjungtas, dar neskaityta jokios eilutės
  • xrcsActive — stovi ant galiojančios eilutės
  • xrcsEof — lapas suvartotas iki galo
  • xrcsCancelled — kvietėjas sąmoningai sustabdė perėjimą
  • xrcsFaulted — perėjimas žlugo ir originali išimtis buvo pakelta

Tas paskutinis skirtumas yra tas, kuris svarbus produkcijoje. Trūkstama darbalapio dalis arba nepavykęs perėjimo startas išlaiko savą EReadError ir perkelia kursorių į xrcsFaulted; jis niekada nenužeminamas iki paprasto False, kurį kvietėjas skaitytų kaip „šis lapas buvo tuščias“. Cancel tyčia siauresnis nei Close: jis uždaro dabartinį darbalapio backendą ir jo inflate substream bei negalioja dabartinę eilutę, bet neišleidžia ZIP archyvo ar šaltinio srauto, o dvigubas iškvietimas yra no-op. Po atšaukimo tęsite kviesdami SelectSheet aiškiai — kursorius nesustartuos perėjimo jūsų vardu tyliai. Srauto nuosavybė seka tą pačią gynybinę taisyklę: xsoBorrowed yra numatytasis ir atstato srauto poziciją uždarant, xsoOwned perkelia nuosavybę tik po to, kai Open jau pavyko, tad nesėkmingas atidarymas niekada neišlaisvina srauto, kurį kvietėjas vis dar laiko

Šešios HotXLS eilučių kursoriaus būsenos su perėjimais tarp jų, rodantys Cancel, perkeliantį aktyvų perėjimą į atšauktą, nepavykusį perėjimo startą, perkeliantį į sugedusį, ir kaip abu lieka atskirti nuo lapo pabaigos
Šešios įvardytos būsenos laiko tuščią lapą, sąmoningą sustojimą ir nepavykusį perėjimą atskiriamais, ko negali vienas Eof booleanas
var
  Cursor: TXLSRowCursor;
  Src: TFileStream;
begin
  Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
  try
    Cursor := TXLSRowCursor.Create;
    try
      // xsoBorrowed: kursorius niekada neišlaisvina Src, o Close atstato
      // poziciją, kurią srautas turėjo, kai buvo kviečiamas Open
      if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
        Exit;

      if Cursor.FindFirst then
        repeat
          if UserPressedStop then
          begin
            Cursor.Cancel;   // uždaro darbalapio backendą ir jo
            Break;           // inflate substream tik; idempotentiškas
          end;
        until not Cursor.FindNext;

      case Cursor.State of
        xrcsEof:       Log('sheet consumed to the end');
        xrcsCancelled: Log('stopped by the operator');
        xrcsFaulted:   Log('pass failed; the EReadError was already raised');
      end;
    finally
      Cursor.Free;
    end;
  finally
    Src.Free;                // vis dar mūsų, vis dar galioja, pozicija atstatyta
  end;
end;

Dabartinės eilutės pasiskolinimas be jos kopijavimo

IXLSRowCursorView perduoda eilutę kitai rutinai nedubliuodama langelių masyvo. Vaizdas saugo bendrą sargą, laikantį kursoriaus rodyklę plus UInt64 kartos skaitiklį; žengimas, lapo pasirinkimas, atšaukimas, uždarymas ir kursoriaus sunaikinimas visi padidina tą kartą, o sunaikinimas papildomai išvalo sargo savininką. Tad pasenęs vaizdas negali skaityti išlaisvintos atminties: Valid yra be išimties probe, kurį galite kviesti bet kada, o kiekvienas kitas narys pirmiausia patvirtina ir kelia EXLSRowCursorViewInvalidated. Būkite sąžiningi dėl to, kas yra šis kontraktas — tai gyvavimo fail-fast, ne gijų saugumo garantija, ir jis nelicencijuoja eilutės skaitymo iš antros gijos, kol pirmoji žengia kursorių

var
  View: IXLSRowCursorView;
  Cell: TXLSRowCursorCell;
  I: Integer;
begin
  if Cursor.FindFirst then
    repeat
      View := Cursor.CurrentRowView;      // pasiskolinta; joks langelių masyvas nenukopijuotas
      for I := 0 to View.CellCount - 1 do
      begin
        Cell := View.Cells[I];
        if Cell.HasFormula and not Cell.FormulaTextAvailable then
          UseCachedResult(Cell.Value)     // BIFF forward skaitymai laiko
        else if Cell.Kind = xdkEmpty then //   talpyklinį rezultatą, o ne tokenus
          UseStyleOnly(Cell.StyleIndex)   // Blank / MulBlank yra tikri langeliai
        else
          UseValue(Cell.Col, Cell.Value);
      end;
    until not Cursor.FindNext;

  // Sąsaja išgyvena ciklą, bet eilutė už jos ne
  if not View.Valid then    // Valid niekada nekelia; Cells[] dabar keltų
    View := nil;            // EXLSRowCursorViewInvalidated
end;

PeakRowBufferedBytes ir tai, ką jam leista įrodyti

PeakRowBufferedBytes egzistuoja, kad pademonstruotų: atmintis seka eilutės plotį, o ne eilučių skaičių. Jis kaupia dabartinės išvesties eilutės langelių įrašus, Variantus, formulės eilutes ir rich-text naštas ir įlenkia formato specifinį darbinį rinkinį — CSV loginį įrašą, ODS fizinės eilutės šabloną, BIFF įrašų piką arba XLSX žalią langelį, kuris dabar dekoduojamas. Skaitykite jį kartu su SheetPassesStarted, skaičiuojančiu, kiek darbalapio perėjimų iš tikrųjų prasidėjo. Du svarstymai laiko tai sąžininga: skaičius yra įvertinimas, ne tiksli krūvos apskaita, ir jis monotoniškas nuo paskiausio Open, tad tai derinimo ir regresijos instrumentas, o ne gyvas matuoklis. Dėl platesnio vaizdo, kur kelia laikas ir baitai ant labai didelių knygų, žr. didelių darbo knygų našumą Delphi

HotXLS palyginimas, rodantis viso lapo įkėlimą, laikantį kiekvieną eilutę gyvenančią, prieš pull kursorių, laikantį tik dabartinę eilutę plus vieną formato darbinį rinkinį, ką PeakRowBufferedBytes kaupia ir praneša
PeakRowBufferedBytes kaupia dabartinę išvesties eilutę plus formato specifinį darbinį rinkinį, tad atmintis seka, kiek plati eilutė, o ne kiek eilučių lapas turi

Push skaitytuvas tapo adapteriu ir ko kursorius nedarys

TXLSForwardReader nebeneša atskirų XLSX, BIFF ir teksto skenavimo įėjimo taškų. Jis sukonfigūruoja kursorių, eina juo ir išverčia dabartinę eilutę į OnSheet ir OnCell įvykius, todėl dvi fasados nebepajėgia dreifuoti filtravimo, formulės būsenos ar klaidų apdorojimo klausimais. Dvi pasekmės vertos žinoti prieš atsinaujinant: callback SheetIndex dabar vienodai skaičiuojamas nuo vieneto ant TXLSForwardReader (TXLSDirectReader išlaiko esamą nuo nulio skaičiuojamą įvykių kontraktą), o OnSheet suveikia prieš SelectSheet, tad SkipSheet nustatymas reiškia, jog darbalapio dalis niekada apskritai neatidaroma ar neišspaudžiama. Ribos vienodai aiškios: darbo knygos negalima keisti, kol perėjimas aktyvus, atšaukimas reikalauja aiškaus restarto, o BIFF forward kelias niekada nedekompiliuoja formulės tokenų, tad klasikiniai formulės langeliai praneša HasFormula true su FormulaTextAvailable false ir duoda jums talpyklinį rezultatą, vietoj išgalvotos tuščios formulės eilutės. Eilučių kursorius ir jo adapteris praėjo 1,298 patikras ant Delphi Win32 ir Win64 plus C++Builder 37.0 Win64 statinio paketo

Jei sveriate pull kursorių prieš įkėlėją, kurį turite dabar, klausimas nėra, kuris greičiau analizuoja, o kuris leidžia parašyti išėjimo sąlygą, kurios iš tikrųjų jums reikia. Pilnos komponento detalės, palaikomos IDE versijos ir licencijavimas yra HotXLS Delphi skaičiuoklių komponento puslapyje