HotXLS leser .xls-, .xlsx-, .xlsm-, .ods-, CSV- og TSV-kilder gjennom én enkelt pull-radmarkør, TXLSRowCursor, hvis FindFirst og FindNext flytter én logisk rad om gangen mens bare den raden blir i minnet. En tilstandsmaskin med seks verdier skiller før-første fra EOF, kansellert og feilet, og den eldre callback-leseren er nå en adapter over samme markør
Scenariet er kjent for alle som har levert en importfunksjon. En .xlsx på 200 MB ankommer, du kobler til en OnCell-behandler, og det første kravet etter «les den» er «stopp etter de første hundre reverserte posteringene». Nå jobber formen på koden din mot deg: løkken bor inne i biblioteket, behandleren din må heise et flagg, hvert etterfølgende callback fyres fortsatt til parseren legger merke til det, og den akkumulerte tilstanden — hvor mange treff så langt, hvilken kolonne som matchet, hva som skjer neste — må bo i felt på en klasse som bare finnes for å gi callback-et et sted å sitte. Ingenting av dette er et parserproblem. Det er et kontrollflytproblem, og det er det en pull-markør fjerner
Hva et push-callback faktisk koster ved 200 MB
Push snur kontrollen, og inversjon er akkurat det en filtrerende eller sammenføyende kaller ikke har råd til. Med et callback-API eier biblioteket løkken, så kalleren kan ikke bruke Break, kan ikke flette to kilder, kan ikke gi leseren til en rutine som forventer å bli drevet, og kan ikke uttrykke «kikk på neste rad før du bestemmer» uten buffering. Kostnaden er ikke gjennomstrømning — en velskrevet SAX-callback-sti strømmer fint — det er at hver ikke-triviell forbruker dyrker sin egen lille tilstandsmaskin for å simulere løkken det ikke var tillatt å skrive. Multipliser det med fire filformater, hver historisk med eget skanneinngangspunkt, og filtrerings-, formel- og feilsemantikken begynner å drive fra hverandre mellom dem, som er nøyaktig drivet HotXLS satte seg fore å lukke
Hvordan endrer en pull-markør kallkoden din?
Den gir løkken tilbake til deg, og med den vanlig Pascal-kontrollflyt. TXLSRowCursor.Open godtar et filnavn eller en TStream, oppdager formatet, laster delte strenger og datostilmetadata én gang, og velger ark 1. SelectSheet (1-basert) eller SelectSheetByName sikter mot et annet regneark og nullstiller markøren til før-første. FindFirst og FindNext posisjonerer deretter på neste befolkede rad — rader uten dekbare celler hoppes over, så RowIndex kan hoppe — og den gjeldende raden eksponeres som CellCount, Cells[] og ValueByCol[], alle 1-basert på kolonneaksen. Å forlate løkken er en Break
var
Cursor: TXLSRowCursor;
Hits: Integer;
begin
Cursor := TXLSRowCursor.Create;
try
Cursor.FirstRow := 2; // hopp over topptekstbandet
Cursor.IncludeColumn(1); // dekod bare disse to kolonnene
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; // vanlig Break; ingen avbrytflagg, ingen sentinel
end;
until not Cursor.FindNext;
finally
Cursor.Free; // destruktoren avslutter passet
end;
end;
Projeksjon og område settes før passet, ikke filtreres etterpå. FirstRow, LastRow, IncludeColumn, ClearColumnProjection, IncludeFormulaText, DetectDates og DetectTextTypes alle æres inne i backends, så en uvalgt kolonne tildeler aldri sin verdi, formelstreng eller rikt-tekst-nyttelast i det hele tatt — regresjonspakken beviser dette med 16 KiB-formler og bufrede strenger som aldri materialiseres når kolonnen deres ikke er projisert. Disse alternativene er bevisst frosset mens et pass er aktivt og blir skrivbare igjen ved EOF, ved SelectSheet eller etter Close, så én skanning kan aldri blande to dekningskontrakter. Hvis du bare trenger arkinventaret snarere enn radene, er kun-metadata og selektiv arkinnlasting det billigere inngangspunktet
Én backend per format, én skanneløkke hver
Hvert format har nøyaktig én fremover-skanner inne i HotXLS, og både pull-markøren og callback-leseren driver samme skanner. TXLSXForwardRowBackend er den eneste regneark-SAX-tilstandsmaskinen for ECMA-376 Part 1 §18.3 arkdeler, og holder XML-leseren, den delte formeltabellen og rikt-tekst-parseren, og den flytter seg til nøyaktig én fysisk <row>-grense per kall. TXLSBiffForwardParser eier globalene, arkvalget og radfremrykkingen for [MS-XLS]-poststrømmen; å gjøre den pausbar produserte den skarpeste begrensningen i hele designet, fordi en bufret strengformel er en Formula-post umiddelbart etterfulgt av en String-post, så et suspensjonspunkt per rad må aldri lande mellom de to. TXLSForwardTextBackend holder en BOM-bevisst leser, det aktive skilletegnet og én logisk post — CSV snuser komma, semikolon, tabulator eller pipe fra første post mens den ignorerer sitattegn, og flerlinjede siterte felt føyes sammen med #10 slik at radnummeret sporer logiske poster snarere enn fysiske linjeskift. TXLSForwardOdsBackend holder én enkelt fysisk radmal for OpenDocument §9-tabeller, behandler table:number-rows-repeated som et gjenstående antall snarere enn en ekspansjon, og flytter seg forbi dekkede celler uten å emittiere verdier. Strømmende direkteleseren deler samme delte-streng- og datostil-laster
Hvorfor seks tilstander i stedet for ett Eof-flagg?
Fordi én enkelt boolsk verdi gjør fire forskjellige situasjoner uatskillelige, og kallere gjetter feil om alle sammen. TXLSRowCursorState navngir dem eksplisitt
xrcsClosed— ingen kilde er åpenxrcsBeforeFirst— åpnet eller re-siktet, ingen rad lest ennåxrcsActive— står på en gyldig radxrcsEof— arket ble konsumert til sluttenxrcsCancelled— kalleren stoppet passet bevisstxrcsFaulted— passet feilet og det opprinnelige unntaket ble utløst
Den siste skillelinjen er den som betyr noe i produksjon. En manglende regnearkdel eller en feilet passstart beholder sin EReadError og flytter markøren til xrcsFaulted; den nedgraderes aldri til en ren False som en kaller ville lese som «dette arket var tomt». Cancel er bevisst smalere enn Close: den lukker den gjeldende regneark-backend-en og dens understrøm og ugyldiggjør den gjeldende raden, men den frigir ikke ZIP-arkivet eller kildestrømmen, og å kalle den to ganger er en no-op. Etter en kansellering gjenopptar du ved å kalle SelectSheet eksplisitt — markøren vil ikke stille starte et pass på dine vegne. Strømeierskap følger samme defensive regel: xsoBorrowed er standard og gjenoppretter strømposisjonen ved lukking, xsoOwned overfører eierskap først etter at Open allerede har lykkes, så en feilet åpning frigir aldri en strøm kalleren fortsatt holder
var
Cursor: TXLSRowCursor;
Src: TFileStream;
begin
Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
try
Cursor := TXLSRowCursor.Create;
try
// xsoBorrowed: markøren frigir aldri Src, og Close gjenoppretter
// posisjonen strømmen hadde da Open ble kalt
if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
Exit;
if Cursor.FindFirst then
repeat
if UserPressedStop then
begin
Cursor.Cancel; // lukker regneark-backend-en og dens
Break; // understrøm bare; idempotent
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; // fortsatt vår, fortsatt gyldig, posisjon gjenopprettet
end;
end;
Låne gjeldende rad uten å kopiere den
IXLSRowCursorView gir en rad til en annen rutine uten å duplisere cellearrayet. Visningen lagrer en delt vokter som holder markørpekeren pluss en UInt64-generasjonsteller; fremrykking, arkvalg, kansellering, lukking og destruksjon av markøren inkrementerer alle den generasjonen, og destruksjon rydder i tillegg vokter-eieren. Så en foreldet visning kan ikke lese frigitt minne: Valid er en unntaksfri sonde du kan kalle når som helst, mens alle andre medlemmer validerer først og utløser EXLSRowCursorViewInvalidated. Vær ærlig på hva denne kontrakten er — det er levetid fail-fast, ikke en trådsikkerhetsgaranti, og den tillater ikke å lese en rad fra en annen tråd mens den første flytter markøren
var
View: IXLSRowCursorView;
Cell: TXLSRowCursorCell;
I: Integer;
begin
if Cursor.FindFirst then
repeat
View := Cursor.CurrentRowView; // låner; ingen cellearray kopieres
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-lesinger beholder det
else if Cell.Kind = xdkEmpty then // bufrede resultatet, ikke tokenene
UseStyleOnly(Cell.StyleIndex) // Blank / MulBlank er ekte celler
else
UseValue(Cell.Col, Cell.Value);
end;
until not Cursor.FindNext;
// Grensesnittet overlever løkken, men raden bak det gjør ikke det
if not View.Valid then // Valid utløser aldri; Cells[] nå ville utløse
View := nil; // EXLSRowCursorViewInvalidated
end;
PeakRowBufferedBytes, og hva den får lov til å bevise
PeakRowBufferedBytes finnes for å demonstrere at minnet sporer radbredde snarere enn radantall. Den akkumulerer cellerecordene, Variantene, formelstrengene og rikt-tekst-nyttelastene til den gjeldende utdataraden og folder inn det formatspesifikke arbeidssettet — den logiske CSV-posten, den fysiske ODS-radmalen, BIFF-posttoppen, eller XLSX-råcellen som dekodes for øyeblikket. Les den sammen med SheetPassesStarted, som teller hvor mange regneark-pass som faktisk begynte. To forbehold holder dette ærlig: tallet er et anslag, ikke nøyaktig heap-regnskap, og det er monotonisk siden siste Open, så det er et feilsøkings- og regresjonsinstrument snarere enn en live-måler. For det bredere bildet av hvor tid og byte går på svært store bøker, se ytelse for store arbeidsbøker i Delphi
Push-leseren ble en adapter, og hva markøren ikke gjør
TXLSForwardReader bærer ikke lenger separate XLSX-, BIFF- og tekst-skanneinngangspunkter. Den konfigurerer en markør, går gjennom den, og oversetter den gjeldende raden til OnSheet- og OnCell-hendelser, og det er derfor de to fasadene ikke lenger kan drive fra hverandre på filtrering, formeltilstand eller feilhåndtering. To konsekvenser er verdt å vite før du oppgraderer: callback-SheetIndex er nå uniformt 1-basert på TXLSForwardReader (TXLSDirectReader beholder sin eksisterende 0-baserte hendelseskontrakt), og OnSheet fyres før SelectSheet, så å sette SkipSheet betyr at regnearkdelen aldri åpnes eller dekomprimeres i det hele tatt. Grensene er like eksplisitte: arbeidsboken må ikke endres mens et pass er aktivt, kansellering krever en eksplisitt omstart, og BIFF forward-stien dekompilerer aldri formeltokens, så klassiske formelceller rapporterer HasFormula true med FormulaTextAvailable false og gir deg det bufrede resultatet i stedet for å finne på en tom formelstreng. Radmarkøren og dens adapter besto 1 298 sjekker på Delphi Win32 og Win64 pluss C++Builder 37.0 Win64 statisk pakke
Hvis du veier en pull-markør mot lasteren du har nå, er spørsmålet å stille ikke hvilken som parser raskere, men hvilken som lar deg skrive utgangsbetingelsen du faktisk trenger. Fullstendige komponentdetaljer, støttede IDE-versjoner og lisensiering finnes på HotXLS Delphi regnearkkomponent-siden