Programování

Použijte RandomAccessFile k vytvoření nízkoúrovňové databáze

Jak jsem hledal JavaWorldStránka s nápady pro tento měsíc Krok za krokem, Narazil jsem jen na několik článků o přístupu k souborům na nízké úrovni. Ačkoli rozhraní API na vysoké úrovni, jako je JDBC, nám poskytují flexibilitu a výkon potřebný pro velké podnikové aplikace, mnoho menších aplikací vyžaduje jednodušší a elegantnější řešení.

V tomto článku vytvoříme rozšíření RandomAccessFile třída, která nám umožňuje ukládat a načítat záznamy. Tento „záznamový soubor“ bude ekvivalentní trvalému hashtable, což umožní ukládat a načítat klíčované objekty z úložiště souborů.

Základní nátěr na spisy a záznamy

Než skočíme do příkladu, začněme základním pozadí. Začneme definováním některých pojmů vztahujících se k souborům a záznamům, poté krátce probereme třídu java.io.RandomAccessFile a závislost na platformě.

Terminologie

Následující definice jsou naladěny spíše na náš příklad než na tradiční databázovou terminologii.

Záznam - Sbírka souvisejících dat uložená v souboru. Záznam má obvykle několik pole, každá je pojmenovaná a napsaná položka informace.

Klíč - Identifikátor záznamu. Klíče jsou obvykle jedinečné.

Soubor - Postupný sběr dat uložených v nějakém stabilním úložišti, například na pevném disku.

Nesekvenční přístup k souborům - Umožňuje číst data z libovolných umístění v souboru.

Ukazatel souboru - Číslo obsahující pozici dalšího bajtu dat, která mají být načtena ze souboru.

Zaznamenejte ukazatel - Ukazatel záznamu je ukazatel souboru, který ukazuje na místo, kde konkrétní záznam začíná.

Index - Sekundární způsob přístupu k záznamům v souboru; to znamená, že mapuje klíče k záznamu ukazatelů.

Halda - Sekvenční soubor neuspořádaných a proměnných záznamů. Halda vyžaduje nějaké externí indexování, aby bylo možné smysluplně přistupovat k záznamům.

Vytrvalost - Odkazuje na uložení objektu nebo záznamu po určitou dobu. Tato doba je obvykle delší než rozpětí jednoho procesu, takže objekty jsou obvykle vytrval v souborech nebo databázích.

Přehled třídy java.io.RandomAccessFile

Třída RandomAccessFile je způsob Java, jak zajistit nesekvenční přístup k souborům. Třída nám umožňuje přeskočit na určité místo v souboru pomocí hledat() metoda. Jakmile je ukazatel souboru umístěn, lze data číst a zapisovat do souboru pomocí DataInput a DataOutput rozhraní. Tato rozhraní nám umožňují číst a zapisovat data nezávislým na platformě. Další užitečné metody v RandomAccessFile dovolte nám zkontrolovat a nastavit délku souboru.

Úvahy závislé na platformě

Moderní databáze se při ukládání spoléhají na diskové jednotky. Data na diskové jednotce jsou uložena v bloky, které jsou distribuovány napříč stopy a povrchy. Disk je hledat čas a rotační zpoždění diktovat, jak lze data nejúčinněji ukládat a načítat. Typický systém správy databáze úzce spoléhá na atributy disku, aby zefektivnil výkon. Bohužel (nebo naštěstí, v závislosti na vašem zájmu o nízkoúrovňový I / O souborů!), Leží tyto parametry daleko od dosahu při použití souborového API na vysoké úrovni, jako je java.io. Vzhledem k této skutečnosti náš příklad ignoruje optimalizace, které by mohla poskytnout znalost parametrů disku.

Návrh příkladu RecordsFile

Nyní jsme připraveni navrhnout náš příklad. Nejprve uvedu některé konstrukční požadavky a cíle, vyřeším problémy se současným přístupem a určím nízkoúrovňový formát souboru. Než přistoupíme k implementaci, podíváme se také na hlavní operace záznamu a jejich odpovídající algoritmy.

Požadavky a cíle

Naším hlavním cílem v tomto příkladu je použít a RandomAccessFile poskytnout způsob ukládání a načítání záznamových dat. Přidružíme klíč typu Tětiva s každým záznamem jako prostředek k jeho jedinečné identifikaci. Klíče budou omezeny na maximální délku, i když nebudou omezena data záznamu. Pro účely tohoto příkladu budou naše záznamy sestávat pouze z jednoho pole - „blob“ binárních dat. Kód souboru se nepokusí žádným způsobem interpretovat záznamová data.

Jako druhý cíl návrhu budeme požadovat, aby počet záznamů, které náš soubor podporuje, nebyl v době vytvoření opraven. Umožníme souboru zvětšovat a zmenšovat se při vkládání a odebírání záznamů. Protože naše indexová a záznamová data budou uložena ve stejném souboru, toto omezení způsobí, že přidáme další logiku k dynamickému zvětšení indexového prostoru reorganizací záznamů.

Přístup k datům v souboru je řádově pomalejší než přístup k datům v paměti. To znamená, že počet přístupů k souborům, které databáze provádí, bude určujícím výkonovým faktorem. Budeme požadovat, aby naše hlavní operace s databází nezávisly na počtu záznamů v souboru. Jinými slovy, budou času konstantní objednávky s ohledem na přístupy k souborům.

Jako poslední požadavek předpokládáme, že náš index je dostatečně malý, aby se mohl načíst do paměti. To usnadní naší implementaci splnění požadavku, který určuje dobu přístupu. Zrcadlíme index v a Hashtable, který poskytuje okamžité vyhledávání záhlaví záznamu.

Oprava kódu

Kód pro tento článek obsahuje chybu, která způsobí, že v mnoha možných případech vyvolá NullPointerException. V abstraktní třídě BaseRecordsFile existuje rutina s názvem insureIndexSpace (int). Kód je určen k přesunutí existujících záznamů na konec souboru, pokud je třeba oblast indexu rozbalit. Poté, co se kapacita prvního záznamu obnoví na skutečnou velikost, se přesune na konec. DataStartPtr je poté nastaven tak, aby ukazoval na druhý záznam v souboru. Bohužel, pokud v prvním záznamu bylo volné místo, nový dataStartPtr nebude ukazovat na platný záznam, protože byl zvýšen o první záznam délka spíše než jeho kapacita. Upravený zdroj Java pro BaseRecordsFile najdete ve zdrojích.

od Rona Walkupa

Senior softwarový inženýr

bioMerieux, Inc.

Synchronizace a souběžný přístup k souborům

Pro zjednodušení začneme tím, že podporujeme pouze model s jedním vláknem, ve kterém je zakázáno souběžně žádat o soubory. Můžeme toho dosáhnout synchronizací metod veřejného přístupu BaseRecordsFile a RecordsFile třídy. Toto omezení můžete uvolnit a přidat podporu pro souběžná čtení a zápisy na nekonfliktní záznamy: Budete muset udržovat seznam uzamčených záznamů a prokládat čtení a zápisy pro souběžné požadavky.

Podrobnosti o formátu souboru

Nyní explicitně definujeme formát souboru záznamů. Soubor se skládá ze tří oblastí, z nichž každá má svůj vlastní formát.

Oblast záhlaví souboru. Tato první oblast obsahuje dvě základní záhlaví potřebná pro přístup k záznamům v našem souboru. První záhlaví zvané ukazatel začátku dat, je dlouho který ukazuje na začátek záznamu dat. Tato hodnota nám říká velikost oblasti indexu. Druhá hlavička s názvem počet záhlaví záznamů, je int který udává počet záznamů v databázi. Oblast záhlaví začíná na prvním bajtu souboru a rozšiřuje se na FILE_HEADERS_REGION_LENGTH bajtů. Použijeme readLong () a readInt () číst záhlaví a writeLong () a writeInt () psát záhlaví.

Oblast indexu. Každá položka v indexu se skládá z klíče a záhlaví záznamu. Index začíná na prvním bajtu za oblastí záhlaví souboru a rozšiřuje se až do bajtu před ukazatelem spuštění dat. Z těchto informací můžeme vypočítat ukazatel souboru na začátek kteréhokoli z n položky v rejstříku. Záznamy mají pevnou délku - klíčová data začínají na prvním bajtu v záznamu indexu a rozšiřují se MAX_KEY_LENGTH bajtů. Odpovídající záhlaví záznamu pro daný klíč následuje bezprostředně za klíčem v indexu. Záhlaví záznamu nám říká, kde jsou data umístěna, kolik bytů může záznam obsahovat a kolik bytů ve skutečnosti obsahuje. Položky rejstříku v indexu souboru nejsou v žádném konkrétním pořadí a nemapují se na pořadí, v jakém jsou záznamy uloženy v souboru.

Zaznamenejte oblast dat. Oblast záznamu dat začíná na místě označeném ukazatelem zahájení dat a sahá až na konec souboru. Záznamy jsou v souboru umístěny zády k sobě, přičemž mezi záznamy není povoleno volné místo. Tato část souboru se skládá ze surových dat bez informací o záhlaví nebo klíči. Soubor databáze končí na posledním bloku posledního záznamu v souboru, takže na konci souboru není žádný další prostor. Soubor se zvětšuje a zmenšuje při přidávání a mazání záznamů.

Velikost přidělená záznamu nemusí vždy odpovídat skutečnému množství dat, které záznam obsahuje. Záznam lze považovat za kontejner - může být jen částečně plný. Platná data záznamu jsou umístěna na začátku záznamu.

Podporované operace a jejich algoritmy

The RecordsFile bude podporovat následující hlavní operace:

  • Vložit - přidá do souboru nový záznam

  • Číst - Přečte záznam ze souboru

  • Aktualizovat - Aktualizuje záznam

  • Odstranit - odstraní záznam

  • Zajistit kapacitu - Vyroste oblast indexu tak, aby vyhovovala novým záznamům

Než projdeme zdrojový kód, projdeme si vybrané algoritmy pro každou z těchto operací:

Vložit. Tato operace vloží nový záznam do souboru. Vložit:

  1. Ujistěte se, že vkládaný klíč již není v souboru obsažen
  2. Ujistěte se, že oblast indexu je dostatečně velká pro další záznam
  3. Najděte v souboru dostatek volného místa pro uložení záznamu
  4. Zapište data záznamu do souboru
  5. Přidejte záhlaví záznamu do indexu

Číst. Tato operace načte požadovaný záznam ze souboru na základě klíče. K načtení záznamu jsme:

  1. Pomocí indexu namapujte daný klíč na záhlaví záznamu
  2. Přejděte dolů na začátek dat (pomocí ukazatele na záznamová data uložená v záhlaví)
  3. Načíst data záznamu ze souboru

Aktualizace. Tato operace aktualizuje existující záznam o nová data a nahradí nová data starými. Kroky pro naši aktualizaci se liší v závislosti na velikosti nových dat záznamu. Pokud nová data zapadají do stávajícího záznamu, jsme:

  1. Zápis dat záznamu do souboru, přepsání předchozích dat
  2. Aktualizujte atribut, který obsahuje délku dat v záhlaví záznamu

V opačném případě, pokud jsou data pro záznam příliš velká, jsme:

  1. Proveďte operaci odstranění u stávajícího záznamu
  2. Proveďte vložení nových dat

Vymazat. Tato operace odebere záznam ze souboru. Chcete-li odstranit záznam, uděláme:

  1. Uvolněte místo přidělené odstraněnému záznamu buď zmenšením souboru, pokud je záznam posledním v souboru, nebo přidáním jeho prostoru do sousedního záznamu

  2. Odeberte záhlaví záznamu z indexu nahrazením položky, která se má odstranit, poslední položkou v indexu; Tím je zajištěno, že index je vždy plný a mezi položkami nejsou žádné prázdné mezery

Zajistěte kapacitu. Tato operace zajistí, že oblast indexu je dostatečně velká, aby pojala další položky. Ve smyčce přesouváme záznamy zepředu na konec souboru, dokud není dostatek místa. K přesunutí jednoho záznamu:

  1. Vyhledejte záhlaví záznamu prvního záznamu v souboru; Všimněte si, že se jedná o záznam s daty v horní části oblasti dat záznamu - nikoli záznam s první hlavičkou v indexu

  2. Přečtěte si data cílového záznamu

  3. Zvětšete soubor o velikost dat cílového záznamu pomocí setLength (dlouhý) metoda v RandomAccessFile

  4. Zapište data záznamu do spodní části souboru

  5. Aktualizujte ukazatel dat v záznamu, který byl přesunut

  6. Aktualizujte globální záhlaví, které odkazuje na data prvního záznamu

Podrobnosti implementace - krokování zdrojového kódu

Nyní jsme připraveni zašpinit si ruce a projít kódem pro příklad. Celý zdroj si můžete stáhnout ze zdrojů.

Poznámka: Ke kompilaci zdroje musíte použít platformu Java 2 (dříve známou jako JDK 1.2).

Třída BaseRecordsFile

BaseRecordsFile je abstraktní třída a je hlavní implementací našeho příkladu. Definuje hlavní přístupové metody a řadu užitečných metod pro manipulaci se záznamy a položkami rejstříku.

$config[zx-auto] not found$config[zx-overlay] not found