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
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 neatidarytasxrcsBeforeFirst— atidarytas arba perjungtas, dar neskaityta jokios eilutėsxrcsActive— stovi ant galiojančios eilutėsxrcsEof— lapas suvartotas iki galoxrcsCancelled— 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
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
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