Java Implementatie van Bestandsorganisaties

Om de werking van de in het dictaat beschreven bestandsorganisaties toe te lichten, zijn een aantal van deze bestandsorganisaties in Java geïmplementeerd. Er zijn Java classes gedefinieerd voor twee recordtypen en een aantal bestandsvormen. Daarnaast is er een Java programma beschikbaar dat gebruik maakt van de geïmplementeerde bestandsorganisaties om een eenvoudig database bestand te manipuleren.

Deze tekst beschrijft de structuur en werking van de voorbeeldcode.

Operaties op files

De gegevens uit een database bestand worden opgeslagen in disk files. In deze voorbeelden houden we ons niet bezig met de structuur van het filesysteem, dat wil zeggen, de manier waarop de files op een disk worden opgeslagen; het operating system regelt die zaken voor ons. Voor de programmeur is een file simpelweg een rij van bytes.

Voor de toegang tot de files maken we gebruik van de Java class RandomAccessFile. Door het aanroepen van methoden van deze class kunnen we de benodigde operaties op files uitvoeren. Bij gebruik van files is het altijd mogelijk dat er fouten optreden (omdat een file niet bestaat, of omdat een disk niet gelezen kan worden). In zo'n geval wordt een IOException gegeven.

Belangrijkste members van de class RandomAccessFile

public class RandomAccessFile {
    public RandomAccessFile(String name, String mode) throws IOException;
    public int read() throws IOException;
    public int read(byte b[]) throws IOException;
    public void readFully(byte b[]) throws IOException;
    public void write(int b) throws IOException;
    public void write(byte b[]) throws IOException;
    public void seek(long pos) throws IOException;
    public long length() throws IOException;
    public void close() throws IOException;
    public int readInt() throws IOException;
    public long readLong() throws IOException;
    public float readFloat() throws IOException;
    public void writeInt(int v) throws IOException;
    public void writeLong(long v) throws IOException;
    public void writeFloat(float v) throws IOException;
}

Openen en sluiten

Door het openen van een file maken we aan het filesysteem kenbaar dat we van plan zijn om operaties op deze file uit te voeren. Het openen van een file gebeurt in Java automatisch tijdens de constructie van het RandomAccessFile object. Als het file nog niet bestaat, wordt het automatisch gecreëerd.

    RandomAccessFile f;
    // Open een file, "rw" betekent lezen en schrijven.
    f = new RandomAccessFile("in271.dat", "rw");
Na de laatste operatie moet de file gesloten worden.
    f.close();              // Sluit de file.

Random lezen en schrijven

Om te lezen vanaf een bepaalde plaats in de file, moet eerst de filepointer verplaatst worden met een seek operatie. Daarna kunnen gegevens gelezen of geschreven worden. De RandomAccessFile class kan waarden van een aantal verschillende datatypen lezen en schrijven.

    f.seek(128);            // Zet de filepointer op het 128e byte.
    f.writeInt(21);         // Schrijf de integer waarde 21.
                            // Voor een integer worden 4 bytes gebruikt.
    f.seek(16);             // Zet de filepointer op het 16e byte.
    num = f.readLong();     // Lees een long waarde.
                            // Voor een long worden 8 bytes gebruikt.

Verlengen en inkorten

De lengte van een bestand kan worden opgevraagd met de length() methode.

    len = f.length();	    // Bepaal de lengte van f in bytes.
Het verlengen van een file gebeurt automatisch wanneer er aan het einde van de file nieuwe gegevens worden geschreven.
    f.seek(f.length());     // Spring naar het eind van de file
    f.writeInt(1);          // Schrijf een integer
Inkorten van een file is met de RandomAccessFile class niet mogelijk. De voorbeeldprogramma's zullen een file dan ook niet verkleinen wanneer er records uit verwijderd worden. In plaats daarvan wordt bijgehouden welke plekken in de file ongebruikt zijn; later kunnen daar weer nieuwe gegevens geschreven worden.

Records

De twee geimplementeerde recordtypes zijn Medewerker en Afdeling. Voor elk recordtype is een Java class gedefinieerd. Deze stammen af van de abstracte superclass Record, waarin een aantal algemene eigenschappen van records worden vastgelegd.

In de rest van de programmacode wordt geen onderscheid meer gemaakt tussen de verschillende recordtypes; er wordt alleen nog met het abstracte type Record gewerkt.

Lezen en schrijven van records

De RandomAccessFile class heeft mogelijkheden voor het lezen en schrijven van primitieve datatypes zoals integers, longs en floats. Echter, om een compleet record te lezen, moeten alle velden van het record één voor één gelezen worden.
Omdat het in de rest van de code nogal veel voorkomt dat we een compleet record willen lezen (en om de verschillen tussen Medewerker en Afdeling te verbergen), zijn er speciale lees en schrijf functies in de definitie van de Record class opgenomen.

    Record r;
    RandomAccessFile f;
    .
    .
    f.seek(128);            // Spring naar het 128e byte.
    r.read(f);              // Lees een record en plaats de gegevens in r.
    .
    .
    f.seek(160);            // Spring naar het 160e byte.
    r.write(f);             // Schrijf het record r in de file.

Eenheid van diskaccess

In het dictaat wordt er van uitgegaan dat alle lees en schrijf acties een hele pagina (vast aantal bytes) omvatten. In Java is het echter niet mogelijk om op zo'n laag niveau de diskdrive te besturen. Het concept 'pagina' is in Java niet zichtbaar. De voorbeeldprogramma's werken daarom met een soort denkbeeldige pagina's. Telkens wanneer het dictaat spreekt van het lezen van een pagina, worden in het programma gewoon alle gegevens uit die denkbeeldige pagina één voor één gelezen.
Een variabele diskaccess telt het totale aantal denkbeeldige pagina's dat gelezen of geschreven wordt. Dit is handig bij het vergelijken van verschillende bestandsorganisaties.

    RandomAccessFile f;
    Record[] pag = new Record[RECORDS_PER_PAGE];
    // Maak een array van Medewerker records.
    for (i == 0; i < RECORDS_PER_PAGE; i++)
        pag[i] = Record.newRecord(Medewerker.class)
    .
    .
    // Lees een pagina uit de file.
    f.seek(adres);          // Spring naar het begin van de pagina.
    for (i == 0; i < RECORDS_PER_PAGE; i++)
        pag[i].read(f);     // Lees een record uit de file.
    diskaccess++;           // Verhoog de diskaccess teller.

Bestandsvormen

Voor elke bestandsvorm is een aparte class gedefinieerd. Allemaal stammen ze af van de abstracte class BestandsOrganisatie, waarin de gemeenschappelijke functionaliteit wordt vastgelegd. Zo ondersteund elke bestandsvorm de 4 operaties insert, retrieve, update en delete.
Het nut hiervan is dat een programma geen onderscheid meer hoeft te maken tussen de verschillende bestandsvormen; de interface tussen het programma en de implementatie van de bestandsorganisatie is altijd hetzelfde.

Openen en sluiten

Bij de constructie van een BestandsOrganisatie object wordt het bestand automatisch geopend. De naam van het bestand en het type records in het bestand moet worden meegegeven. Als een bestand met de opgegeven naam nog niet bestaat, wordt er automatisch een leeg bestand aangemaakt.
Wanneer er een fout optreed wordt een IOException gegeven.

Voor het opslaan van één bestand worden vaak meerdere diskfiles gebruikt, dit is afhankelijk van de gebruikte bestandsorganisatie.

    BestandsOrganisatie bestand;
    // Open een BBoom bestand met Medewerker records.
    bestand = new BBoom("data", Medewerker.class);
Het sluiten van het bestand gebeurt door het aanroepen van de close methode.
    bestand.close();        // Sluit het bestand af.

Retrieve

De retrieve methode zoekt naar het record met de opgegeven sleutel. Het record wordt gelezen en als resultaat teruggegeven. Als het record niet wordt gevonden wordt null teruggegeven.
Als er een fout optreed wordt een IOException gegeven.

    Medewerker m;
    m = bestand.retrieve(1234);  // Zoek de medewerker met m# 1234.

Insert en update

Met insert kunnen records worden toegevoegd. Als er al een record met dezelfde sleutel bestaat, wordt een Exception gegeven.

    Medewerker m = new Medewerker(
      38, "Flip", "Mekelweg", "Delft", new Date(70, 4, 3), "docent", 3);
    bestand.insert(m);      // Voeg medewerker Flip toe.
Door middel van een update kunnen gegevens van een bestaand record worden gewijzigd. Het updaten van een niet bestaand record veroorzaakt een Exception.
    Medewerker m = new Medewerker(
      38, "Flip", "Mekelweg", "Delft", new Date(70, 4, 3), "hoofddocent", 3);
    bestand.update(m);      // Promotie voor Flip.

Delete

Een record kan worden verwijderd door het te deleten. Als er geen record met de opgegeven sleutel bestaat, wordt een Exception gegeven.

    bestand.delete(38);     // Verwijder het record van Flip.

TestApp

TestApp is een Java programma waarmee de implementatie van de bestandsvormen kan worden uitgeprobeerd. Via een aantal dialoogvensters kunnen de elementaire operaties op een database bestand worden uitgevoerd.