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:
- Ujistěte se, že vkládaný klíč již není v souboru obsažen
- Ujistěte se, že oblast indexu je dostatečně velká pro další záznam
- Najděte v souboru dostatek volného místa pro uložení záznamu
- Zapište data záznamu do souboru
- 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:
- Pomocí indexu namapujte daný klíč na záhlaví záznamu
- Přejděte dolů na začátek dat (pomocí ukazatele na záznamová data uložená v záhlaví)
- 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:
- Zápis dat záznamu do souboru, přepsání předchozích dat
- 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:
- Proveďte operaci odstranění u stávajícího záznamu
- Proveďte vložení nových dat
Vymazat. Tato operace odebere záznam ze souboru. Chcete-li odstranit záznam, uděláme:
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
- 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:
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
Přečtěte si data cílového záznamu
Zvětšete soubor o velikost dat cílového záznamu pomocí
setLength (dlouhý)
metoda vRandomAccessFile
Zapište data záznamu do spodní části souboru
Aktualizujte ukazatel dat v záznamu, který byl přesunut
- 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.