HotXLS lukee .xls-, .xlsx-, .xlsm-, .ods-, CSV- ja TSV-lähteet yhden pull-rivikursorin kautta: TXLSRowCursor, jonka FindFirst ja FindNext etenevät yhden loogisen rivin kerrallaan, ja vain kyseinen rivi pysyy muistissa. Kuuden arvon tilakone erottaa ensimmäistä edeltävän tilan EOF:sta, perutusta ja epäonnistuneesta, ja vanhempi callback-lukija on nyt adapteri saman kursorin päällä
Tilanne on tuttu jokaiselle, joka on toimittanut tuontiominaisuuden. 200 megatavun .xlsx saapuu, kytket OnCell-käsittelijän, ja ensimmäinen vaatimus luku-ohjeen jälkeen on pysähtyä sadan ensimmäisen peruutetun kirjauksen jälkeen. Nyt koodisi muoto taistelee sinua vastaan: silmukka asuu kirjaston sisällä, käsittelijäsi joutuu nostamaan lipun, jokainen myöhempi callback laukeaa yhä, kunnes jäsennin huomaa, ja kertynyt tila — montako osumaa tähän mennessä, mikä sarake osui, mitä tehdä seuraavaksi — joutuu asumaan luokan kenttiin, joka on olemassa vain antaakseen callbackille paikan istua. Mitään siinä ei ole jäsennysongelma. Se on ohjausvirtaongelma, ja se on se, jonka pull-kursori poistaa
Mitä push-callback oikeasti maksaa 200 megatavussa
Push kääntää ohjauksen väärinpäin, ja käänteisyys on juuri se, mitä suodattava tai liittävä kutsuja ei voi maksaa. Callback-API:lla kirjasto omistaa silmukan, joten kutsuja ei voi käyttää Breakia, ei voi lomittaa kahta lähdettä, ei voi antaa lukijaa rutiinille, joka odottaa tulleensa ajettavaksi, eikä voi ilmaista kurkista seuraavaa riviä ennen päätöstä ilman puskurointia. Hintana ei ole läpäisy — hyvin kirjoitettu SAX-callback-polku suoratoistaa ihan hyvin — vaan se, että jokainen ei-triviaali kuluttaja kasvattaa pienen oman tilakoneensa simuloidakseen silmukkaa, jota ei saanut kirjoittaa. Kerro se neljällä tiedostomuodolla, joista kullakin on historiansa aikana ollut oma skannauspäätepisteensä, ja suodatus-, kaava- ja virhesemantiikka alkavat ajautua erilleen niiden välillä, mikä on täsmälleen se ajautuminen, jonka sulkemiseen HotXLS lähti
Kuinka pull-kursori muuttaa kutsukoodiasi?
Se antaa silmukan takaisin sinulle, ja mukana tavallisen Pascal-ohjausvirran. TXLSRowCursor.Open hyväksyy tiedostonimen tai TStream-olion, tunnistaa muodon, lataa jaetut merkkijonot ja päivämäärätyylimetadatan kerran ja valitsee arkin 1. SelectSheet (1-pohjainen) tai SelectSheetByName kohdistaa toiseen työarkiin ja nollaa kursorin ensimmäistä edeltävään tilaan. FindFirst ja FindNext asettuvat sitten seuraavalle täytetylle riville — rivit ilman dekoodattavia soluja ohitetaan, joten RowIndex voi hypätä — ja nykyinen rivi on näkyvillä kohteina CellCount, Cells[] ja ValueByCol[], kaikki 1-pohjaisia sarakkeakselilla. Silmukasta poistuminen on Break
var
Cursor: TXLSRowCursor;
Hits: Integer;
begin
Cursor := TXLSRowCursor.Create;
try
Cursor.FirstRow := 2; // ohita otsikkokaista
Cursor.IncludeColumn(1); // dekoodaa vain nämä kaksi saraketta
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; // tavallinen Break; ei abort-lippua, ei vartijaa
end;
until not Cursor.FindNext;
finally
Cursor.Free; // destruktori lopettaa kierroksen
end;
end;
Projisointi ja alue asetetaan ennen kierrosta, ei suodateta jälkikäteen. Kohteet FirstRow, LastRow, IncludeColumn, ClearColumnProjection, IncludeFormulaText, DetectDates ja DetectTextTypes kunnioitetaan kaikissa taustajärjestelmissä, joten valitsematon sarake ei koskaan varaa arvoaan, kaavamerkkijonoaan tai riketekstipakettiaan alun perinkään — regressiotestisarja todistaa tämän 16 KiB:n kaavoilla ja välimuistisilla merkkijonoilla, joita ei koskaan materialisoida, kun niiden saraketta ei projisoida. Nämä valinnat jäädytetään tarkoituksella kierroksen ollessa aktiivinen ja muuttuvat taas kirjoitettaviksi EOF:ssa, SelectSheetissä tai Close-kutsun jälkeen, joten yksi skannaus ei voi koskaan sekoittaa kahta dekoodaussopimusta. Jos tarvitset vain arkkilistan eikä rivejä, vain metadatan ja valikoivan arkkilatauksen kautta pääsee halvemmalla
Yksi taustajärjestelmä per muoto, yksi skannaussilmukka kullekin
Jokaisella muodolla on täsmälleen yksi eteenpäin skannaava lukija HotXLS:n sisällä, ja sekä pull-kursori että callback-lukija ajavat samaa lukijaa. TXLSXForwardRowBackend on ainoa työarkkien SAX-tilakone ECMA-376 Part 1 §18.3 -arkkiosille, ja se pitää hallussaan XML-lukijan, jaettujen kaavojen taulun ja riketekstijäsennimen, ja etenee täsmälleen yhden fyysisen <row>-rajan per kutsu. TXLSBiffForwardParser omistaa globaalit, arkin valinnan ja rivin etenemisen [MS-XLS]-tietuevirralle; sen tekeminen keskeytettäväksi tuotti terävimmän rajoitteen koko suunnittelussa, koska välimuistissa oleva merkkijonokaava on Formula-tietue, jota seuraa välittömästi String-tietue, joten rivikohtainen keskeytyskohta ei saa koskaan osua niiden väliin. TXLSForwardTextBackend pitää BOM-tietoisen lukijan, aktiivisen erottimen ja yhden loogisen tietueen — CSV nuuhkii pilkun, puolipisteen, sarkaimen tai putken ensimmäisestä tietueesta laiminlyöden lainattuja merkkejä, ja moniriviset lainatut kentät yhdistetään #10:llä, joten rivinumero seuraa loogisia tietueita eikä fyysisiä rivinvaihtoja. TXLSForwardOdsBackend pitää yhtä fyysistä rivipohjaa OpenDocument §9 -tauluille, käsittelee kohteen table:number-rows-repeated jäljellä olevan lukumääränä laajennuksen sijaan ja etenee peitettyjen solujen ohi emittoimatta arvoja. Suoratoistava suora lukija jakaa saman jaettujen merkkijonojen ja päivämäärätyylien lataajan
Miksi kuusi tilaa yhden Eof-lipun sijaan?
Koska yksi totuusarvo tekee neljästä eri tilanteesta erottamattomia, ja kutsujat arvaavat väärin niissä kaikissa. TXLSRowCursorState nimeää ne eksplisiittisesti
xrcsClosed— mitään lähdettä ei ole avattuxrcsBeforeFirst— avattu tai uudelleenkohdistettu, yhtään riviä ei vielä luettuxrcsActive— seisoa kelvollisella rivilläxrcsEof— arkki kulutettiin loppuunxrcsCancelled— kutsuja pysäytti kierroksen tarkoituksellaxrcsFaulted— kierros epäonnistui ja alkuperäinen poikkeus nostettiin
Viimeinen ero on se, joka merkitsee tuotannossa. Puuttuva työarkkiosa tai epäonnistunut kierroksen aloitus säilyttää kohteen EReadError ja siirtää kursorin kohteeseen xrcsFaulted; sitä ei koskaan alenneta tavalliseksi Falseksi, jonka kutsuja lukisi tämä arkki oli tyhjä. Cancel on tarkoituksella kapeampi kuin Close: se sulkee nykyisen työarkin taustajärjestelmän ja sen inflate-alivirran ja mitätöi nykyisen rivin, mutta se ei vapauta ZIP-arkistoa eikä lähdevirtaa, ja sen kutsuminen kahdesti on ei-toiminto. Peruutuksen jälkeen jatkat kutsumalla SelectSheet-kutsua eksplisiittisesti — kursori ei käynnistä kierrosta hiljaa puolestasi. Virran omistus noudattaa samaa puolustussääntöä: xsoBorrowed on oletus ja palauttaa virran sijainnin sulussa, xsoOwned siirtää omistuksen vain, kun Open on jo onnistunut, joten epäonnistunut avaus ei koskaan vapauta virtaa, jota kutsuja yhä pitää
var
Cursor: TXLSRowCursor;
Src: TFileStream;
begin
Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
try
Cursor := TXLSRowCursor.Create;
try
// xsoBorrowed: kursori ei koskaan vapauta Src:tä, ja Close palauttaa
// sijainnin, jonka virta oli, kun Open kutsuttiin
if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
Exit;
if Cursor.FindFirst then
repeat
if UserPressedStop then
begin
Cursor.Cancel; // sulkee vain työarkin taustajärjestelmän ja sen
Break; // inflate-alivirran; idempotentti
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; // yhä meidän, yhä kelvollinen, sijainti palautettu
end;
end;
Nykyisen rivin lainaaminen kopioimatta sitä
IXLSRowCursorView antaa rivin toiselle rutiinille monistamatta solutaulukkoa. Näkymä tallentaa jaetun vartijan, joka pitää kursoriosoittimen plus UInt64-sukupolvelaskurin; eteneminen, arkin valinta, peruutus, sulkeminen ja kursorin tuhoaminen kasvattavat kaikki kyseistä sukupolvea, ja tuhoaminen lisäksi tyhjentää vartijan omistajan. Vanhentunut näkymä ei siis voi lukea vapautettua muistia: Valid on poikkeukseton tunnistin, jota voit kutsua milloin tahansa, kun taas jokainen muu jäsen validoi ensin ja nostaa kohteen EXLSRowCursorViewInvalidated. Ole rehellinen sen suhteen, mikä tämä sopimus on — se on elinkaaren fail-fast, ei säieturvallisuustakuu, eikä se lupaa lukea riviä toisesta säikeestä, kun ensimmäinen etenee kursorissa
var
View: IXLSRowCursorView;
Cell: TXLSRowCursorCell;
I: Integer;
begin
if Cursor.FindFirst then
repeat
View := Cursor.CurrentRowView; // lainaa; solutaulukkoa ei kopioida
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 -luvut säilyttävät
else if Cell.Kind = xdkEmpty then // välimuistituloksen, ei tokeneita
UseStyleOnly(Cell.StyleIndex) // Blank / MulBlank ovat oikeita soluja
else
UseValue(Cell.Col, Cell.Value);
end;
until not Cursor.FindNext;
// Rajapinta elää silmukan yli, mutta sen takana oleva rivi ei
if not View.Valid then // Valid ei koskaan nosta; Cells[] nyt nostaisi
View := nil; // EXLSRowCursorViewInvalidated
end;
PeakRowBufferedBytes ja mitä se saa todistaa
PeakRowBufferedBytes on olemassa osoittamaan, että muisti seuraa rivin leveyttä eikä rivien määrää. Se kasauttaa nykyisen tulosterivin solutietueet, Variantit, kaavamerkkijonot ja riketekstipaketit ja taitaa sisään muotokohtaisen työjoukon — CSV:n loogisen tietueen, ODS:n fyysisen rivipohjan, BIFF:n tietuehuipun tai XLSX:n raakasolun, jota parhaillaan dekoodataan. Lue se yhdessä kohteen SheetPassesStarted kanssa, joka laskee, kuinka monta työarkkikierrosta oikeasti alkoi. Kaksi varausta pitää tämän rehellisenä: luku on arvio, ei tarkka keotilinpito, ja se on monotoninen viimeisimmän Open-kutsun jälkeen, joten se on debuggaus- ja regressiinstrumentti eikä live-mittari. Laajemman kuvan siitä, mihin aika ja tavut menevät erittäin suurissa työkirjoissa, antaa suurten työkirjojen suorituskyky Delphissä
Push-lukijasta tuli adapteri, ja mitä kursori ei tee
TXLSForwardReader ei enää kanna erillisiä XLSX-, BIFF- ja tekstiskannauksen sisääntulopisteitä. Se konfiguroi kursorin, kulkee sen läpi ja kääntää nykyisen rivin kohteiksi OnSheet ja OnCell -tapahtumiksi, minkä vuoksi kaksi julkisivua ei voi enää ajautua erilleen suodatuksessa, kaavatilassa tai virhekäsittelyssä. Kaksi seurausta kannattaa tietää ennen päivitystä: callbackin SheetIndex on nyt yhtenäisesti 1-pohjainen kohteessa TXLSForwardReader (TXLSDirectReader säilyttää olemassa olevan 0-pohjaisen tapahtumasopimuksensa), ja OnSheet laukeaa ennen SelectSheetia, joten kohteen SkipSheet asettaminen tarkoittaa, että työarkkiosaa ei koskaan avata tai pureta lainkaan. Rajat ovat yhtä lailla eksplisiittiset: työkirjaa ei saa muokata kierroksen ollessa aktiivinen, peruutus vaatii eksplisiittisen uudelleenkäynnistyksen, ja BIFF forward -polku ei koskaan dekompileoi kaavatokeneja, joten klassiset kaavasolut raportoivat kohteen HasFormula todeksi kohteen FormulaTextAvailable ollessa epätosi ja antavat sinulle välimuistituloksen keksimättä tyhjää kaavamerkkijonoa. Rivikursori ja sen adapteri läpäisivät 1 298 tarkistusta Delphi Win32:lla ja Win64:llä plus C++Builder 37.0 Win64 -staattisessa paketissa
Jos punnitset pull-kursoria vastaan sen lukijan kanssa, jonka sinulla nyt on, kysymys ei ole siitä, kumpi jäsentää nopeammin, vaan siitä, kumpi antaa sinun kirjoittaa poistumisehdon, jonka oikeasti tarvitset. Täydelliset komponenttitiedot, tuetut IDE-versiot ja lisensöinti ovat HotXLS Delphi -taulukkolaskentakomponentin sivulla