Műszaki cikk

Pull sor-kurzor XLS, XLSX, ODS és CSV-hez Delphiben

A HotXLS .xls, .xlsx, .xlsm, .ods, CSV és TSV forrásokat egyetlen pull sor-kurzorral, a TXLSRowCursor-ral olvas, amelynek FindFirst és FindNext metódusai egyszerre egy logikai sort léptetnek, miközben csak az a sor marad a memóriában. Egy hatértékű állapotgép különíti el a first előttit az EOF-tól, a megszakítotttól és a hibázottól, és a régebbi visszahívásos olvasó ma ugyanannak a kurzornak az adaptora

A forgatókönyv ismerős mindenki számára, aki szállított már importfunkciót. Beérkezik egy 200 MB-os .xlsx, beköti az Ön OnCell kezelőjét, és az olvasd el után az első követelmény az, hogy állj meg az első száz sztornózott tétel után. Most a kódja alakja harcol Ön ellen: a ciklus a könyvtárban él, a kezelőjének jelzőt kell emelnie, minden későbbi visszahívás tovább tüzel, amíg az elemző észre nem veszi, és a felgyülemlett állapot — eddig hány találat, melyik oszlop egyezett, mi a következő lépés — egy olyan osztály mezőiben kell éljen, amely csak azért létezik, hogy a visszahívásnak helyet adjon. Ebben semmi sem elemzési probléma. Vezérlésfolyam-probléma, és ez az, amelyet egy pull kurzor eltávolít

Mit költesz valójában egy push visszahívás 200 MB-on

A push megfordítja a vezérlést, és a megfordítás pontosan az, amit egy szűrő vagy összekapcsoló hívó nem engedhet meg magának. Visszahívásos API esetén a könyvtár birtokolja a ciklust, így a hívó nem használhat Break-et, nem fűzhet össze két forrást, nem adhatja az olvasót egy vezetést váró rutinnak, és nem fejezheti ki a tekints rá a következő sorra, mielőtt döntene pufferelés nélkül. A költség nem az áteresztőképesség — egy jól megírt SAX visszahívásos útvonal szépen streamel —, hanem az, hogy minden nem triviális fogyasztó saját kis állapotgépet növeszt, hogy szimulálja azt a ciklust, amelyet nem írhatott meg. Szorozza meg ezt négy fájlformátummal, amelyek mindegyike történetileg saját pásztázási belépési ponttal bír, és a szűrési, képlet- és hibaszemantika elkezdi szétválni köztük, ami pontosan az a sodródás, amelyet a HotXLS bezárni indult

Hogyan változtatja meg egy pull kurzor a hívó kódját?

Visszaadja Önnek a ciklust, és vele együtt a szokásos Pascal vezérlésfolyamot. A TXLSRowCursor.Open fájlnevet vagy TStream-et fogad, felismeri a formátumot, egyszer tölti be a megosztott sztringeket és dátumstílus-metaadatokat, és az 1. munkalapot választja. A SelectSheet (1 alapú) vagy a SelectSheetByName újracéloz egy másik munkalapot, és a kurzort first előtti állapotba állítja vissza. A FindFirst és FindNext ezután a következő feltöltött sorra pozicionál — a dekódolható cellákkal nem rendelkező sorok átugródnak, így a RowIndex ugorhat —, és az aktuális sor CellCount-ként, Cells[]-ként és ValueByCol[]-ként kerül felszínre, mind 1 alapú az oszloptengelyen. A ciklus elhagyása egy Break

var
  Cursor: TXLSRowCursor;
  Hits: Integer;
begin
  Cursor := TXLSRowCursor.Create;
  try
    Cursor.FirstRow := 2;        // a fejlécsáv átugrása
    Cursor.IncludeColumn(1);     // csak ezt a két oszlopot dekódolja
    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;               // közönséges Break; nincs abort jelző, nincs őrjel
        end;
      until not Cursor.FindNext;
  finally
    Cursor.Free;                 // a destruktor lezárja a menetet
  end;
end;

A projekció és a tartomány a menet előtt állítódik be, nem utólag szűrődik. A FirstRow, LastRow, IncludeColumn, ClearColumnProjection, IncludeFormulaText, DetectDates és DetectTextTypes mind a backendek belsejében érvényesülnek, így egy ki nem választott oszlop először sem allokálja az értékét, képletsztringjét vagy rich-text terhelését — a regressziós készlet ezt 16 KiB-os képletekkel és gyorsítótárazott sztringekkel bizonyítja, amelyek sosem materializálódnak, amikor az oszlopuk nincs kivetítve. E lehetőségek szándékosan fagynak, amíg egy menet aktív, és EOF-nál, SelectSheet-nél vagy Close után újra írhatóvá válnak, így egy pásztázás sosem keverhet két dekódolási szerződést. Ha csak a munkalap-leltárra van szüksége, nem a sorokra, a csak metaadat és szelektív munkalapbetöltés az olcsóbb belépési pont

Formátumonként egy backend, mindegyiknek egy pásztázási ciklusa

Minden formátumnak pontosan egy előrenéző pásztázója van a HotXLS-en belül, és mind a pull kurzor, mind a visszahívásos olvasó ugyanazt a pásztázót hajtja. A TXLSXForwardRowBackend az egyetlen munkalap SAX állapotgép az ECMA-376 Part 1 §18.3 munkalaprészekhez, amely tartja az XML olvasót, a megosztott képlet táblát és a rich-text elemzőt, és hívásonként pontosan egy fizikai <row> határig lép. A TXLSBiffForwardParser birtokolja a globálisokat, a munkalapválasztást és a sorléptetést a [MS-XLS] rekord streamhez; felüggeszthetővé tétele adta a teljes tervezés legélesebb korlátját, mert egy gyorsítótárazott sztringes képlet egy Formula rekord, amelyet azonnal követ egy String rekord, így egy soronkénti felfüggesztési pont sosem landolhat a kettő között. A TXLSForwardTextBackend tart egy BOM-tudatos olvasót, az aktív elválasztót és egy logikai rekordot — a CSV az első rekordból szaglássza ki a vesszőt, pontosvesszőt, tabulátort vagy csövet, idézett karakterek figyelmen kívül hagyásával, és a több soros idézett mezők #10-nel fűzőnek össze, így a sorszám logikai rekordokat követ, fizikai újsorokat nem. A TXLSForwardOdsBackend egyetlen fizikai sor sablont tart az OpenDocument §9 táblákhoz, a table:number-rows-repeated-t maradó számként kezeli, kibontás helyett, és a lefedett cellákon lép át értékek kibocsátása nélkül. A streaming közvetlen olvasó ugyanazt a megosztott sztring- és dátumstílus-betöltőt osztja

A HotXLS pull sor-kurzor formátumonként egy előre pásztázóra diszpecsíroz, SAX backend XLSX-hez, rekord elemző BIFF-hez, elválasztót szaglászó szövegbackend és ODS sorsablon, felül a visszahívásos olvasó adapterként konfigurálva
Minden formátumnak pontosan egy előre pásztázója van, és mind a pull kurzor, mind a visszahívásos olvasó ugyanazt a pásztázót hajtja, így a szűrési és hibaszemantika nem sodródhat szét

Miért hat állapot egyetlen Eof jelző helyett?

Mert egyetlen boolean négy különböző helyzetet tesz megkülönböztethetetlenné, és a hívók mindről tévesen találgatnak. A TXLSRowCursorState explicit megnevezi őket

  • xrcsClosed — nincs nyitott forrás
  • xrcsBeforeFirst — megnyitva vagy újracélozva, még egy sort sem olvastunk
  • xrcsActive — érvényes soron áll
  • xrcsEof — a munkalap a végéig elfogyott
  • xrcsCancelled — a hívó szándékosan leállította a menetet
  • xrcsFaulted — a menet elbukott, és az eredeti kivétel eldobásra került

Ez az utolsó különbségtétel az, amely a termelésben számít. Egy hiányzó munkalaprész vagy egy elbukó menetindítás megtartja az EReadError-ját, és a kurzort xrcsFaulted-re mozgatja; sosem minősül le egyszerű False-dá, amelyet a hívó annak olvasna, hogy ez a munkalap üres volt. A Cancel szándékosan szűkebb, mint a Close: bezárja az aktuális munkalap backendet és annak felfújás alstreamjét, és érvényteleníti az aktuális sort, de nem bocsátja ki a ZIP archívumot vagy a forrás streamet, és kétszeri meghívása no-op. Egy megszakítás után a folytatáshoz explicit módon hívja a SelectSheet-et — a kurzor nem indít újra csendesen menetet az Ön nevében. A stream tulajdonlás ugyanazt a védő szabályt követi: az xsoBorrowed az alapértelmezés, és bezáráskor visszaállítja a stream pozícióját, az xsoOwned csak akkor ruház át tulajdont, amikor az Open már sikeres volt, így egy elbukó nyitás sosem szabadít fel olyan streamet, amelyet a hívó még tart

A HotXLS sor-kurzor hat állapota a köztük lévő átmenetekkel, bemutatva, hogy a Cancel egy aktív menetet megszakítottba mozgat, egy elbukó menetindítás hibázottba, és hogy mindkettő hogyan marad elkülönítve a munkalap végétől
Hat megnevezett állapot tartja elkülönítve az üres munkalapot, a szándékos leállást és az elbukott menetet, amit egyetlen Eof boolean nem tud
var
  Cursor: TXLSRowCursor;
  Src: TFileStream;
begin
  Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
  try
    Cursor := TXLSRowCursor.Create;
    try
      // xsoBorrowed: a kurzor sosem szabadítja fel a Src-t, és a Close visszaállítja
      // azt a pozíciót, amelyen a stream az Open hívásakor volt
      if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
        Exit;

      if Cursor.FindFirst then
        repeat
          if UserPressedStop then
          begin
            Cursor.Cancel;   // bezárja a munkalap backendet és annak
            Break;           // felfújás alstreamjét csak; idempotens
          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;                // még mindig a miénk, még érvényes, pozíció visszaállítva
  end;
end;

Az aktuális sor kölcsönzése másolás nélkül

Az IXLSRowCursorView egy másik rutinnak ad át egy sort a cellatömb duplikálása nélkül. A nézet egy megosztott őrt tárol, amely a kurzor pointert és egy UInt64 generációs számlálót tart; a léptetés, munkalapválasztás, megszakítás, bezárás és a kurzor megsemmisítése mind növeli azt a generációt, a megsemmisítés ráadásul törli az őr tulajdonosát. Így egy elavult nézet nem olvashat felszabadított memóriát: a Valid kivételmentes szonda, amelyet bármikor hívhat, minden más tag viszont először validál, és EXLSRowCursorViewInvalidated-t dob. Legyen őszinte azzal kapcsolatban, mi ez a szerződés — élettartami gyors hibázás, nem szálbiztonsági garancia, és nem jogosít fel arra, hogy egy második szálon olvasson sort, miközben az első lépteti a kurzort

var
  View: IXLSRowCursorView;
  Cell: TXLSRowCursorCell;
  I: Integer;
begin
  if Cursor.FindFirst then
    repeat
      View := Cursor.CurrentRowView;      // kölcsönöz; nem másolódik cellatömb
      for I := 0 to View.CellCount - 1 do
      begin
        Cell := View.Cells[I];
        if Cell.HasFormula and not Cell.FormulaTextAvailable then
          UseCachedResult(Cell.Value)     // a BIFF előre olvasások megtartják a
        else if Cell.Kind = xdkEmpty then //   gyorsítótárazott eredményt, nem a tokéneket
          UseStyleOnly(Cell.StyleIndex)   // Blank / MulBlank valós cellák
        else
          UseValue(Cell.Col, Cell.Value);
      end;
    until not Cursor.FindNext;

  // Az interfész túléli a ciklust, de mögötte a sor nem
  if not View.Valid then    // A Valid sosem dob; a Cells[] most dobna
    View := nil;            // EXLSRowCursorViewInvalidated
end;

PeakRowBufferedBytes, és amit bizonyítania szabad

A PeakRowBufferedBytes azért létezik, hogy bemutassa: a memória a sor szélességét követi, nem a sorok számát. Az aktuális kimeneti sor cellarekordjait, Variant-jait, képletsztringjeit és rich-text terheléseit halmozza, és beleszámítja a formátumspecifikus munkakészletet — a CSV logikai rekordot, az ODS fizikai sorsablont, a BIFF rekordcsúcsot vagy az éppen dekódolt XLSX nyers cellát. Olvassa együtt a SheetPassesStarted-del, amely azt számolja, hány munkalap menet indult el ténylegesen. Két kikötés tartja ezt őszintén: a szám becslés, nem pontos kupacszámadás, és a legutóbbi Open óta monoton, így hibakeresési és regressziós műszer, nem élő mérőóra. A hol mennek az idő és a bájtok nagyon nagy munkafüzeteken kérdés szélesebb képéhez lásd a nagy munkafüzet teljesítményét Delphiben

HotXLS összehasonlítás, amely egy egész munkalapos betöltést mutat, minden sort rezidensként tartva, szemben a pull kurzorral, amely csak az aktuális sort tartja plusz egy formátum munkakészletét, amit a PeakRowBufferedBytes halmoz és jelent
A PeakRowBufferedBytes az aktuális kimeneti sort halmozza plusz a formátumspecifikus munkakészletet, így a memória azt követi, milyen széles egy sor, nem azt, hány sora van a munkalapnak

A push olvasó adaptorrá vált, és amit a kurzor nem tesz

A TXLSForwardReader már nem hordoz külön XLSX, BIFF és szöveg pásztázási belépési pontokat. Kurzort konfigurál, végigsétál rajta, és az aktuális sort OnSheet és OnCell eseményekké fordítja, ezért a két homlokzat már nem sodródhat szét a szűrés, képletállapot vagy hibakezelés dolgában. Két következményt érdemes ismerni a frissítés előtt: a visszahívás SheetIndex-e most egységesen 1 alapú a TXLSForwardReader-en (a TXLSDirectReader megtartja meglévő 0 alapú eseményszerződését), és az OnSheet a SelectSheet előtt tüzel, így a SkipSheet beállítása azt jelenti, hogy a munkalaprész sosem nyílik meg vagy bomlik ki egyáltalán. A határok ugyanilyen explicit: a munkafüzetet nem szabad módosítani, amíg menet aktív, a megszakítás explicit újraindítást követel, és a BIFF előre útvonal sosem dekompilál képlettokéneket, így a klasszikus képletcellák HasFormula igazat jelentenek FormulaTextAvailable hamissal, és a gyorsítótárazott eredményt adják, üres képletsztring kitalálása helyett. A sor-kurzor és adaptora 1 298 ellenőrzésen ment át Delphi Win32 és Win64, plusz a C++Builder 37.0 Win64 statikus csomag alatt

Ha pull kurzort mérlegel a mostani betöltője ellenében, a kérdés nem az, melyik elemz gyorsabban, hanem az, melyik teszi lehetővé, hogy megírja azt a kilépési feltételt, amelyet valójában szükséges. A komponens teljes részletei, a támogatott IDE verziók és a licencelés a HotXLS Delphi spreadsheet komponens oldalán találhatók