From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+In diesem Abschnitt wollen wir uns die zuvor eingeführten Konzepte
+in der Praxis ansehen. Dazu erzeugen wir eine einfache Datenbank DirDB,
+die Informationen zu Dateien und Verzeichnissen speichern kann. Über
+eine einfache Kommandozeilenschnittstelle können die Tabellen
+mit den Informationen aus dem lokalen Dateisystem gefüllt und
+auf unterschiedliche Weise abgefragt werden.
+
+
+DirDB besitzt lediglich zwei Tabellen dir und file
+für Verzeichnisse und Dateien. Sie haben folgende Struktur:
+
+
+
+Tabelle 42.2: Die Struktur der dir-Tabelle
+
+Tabelle 42.3: Die Struktur der file-Tabelle
+Beide Tabellen besitzen einen Primärschlüssel, der beim
+Anlegen eines neuen Satzes vom Programm vergeben wird. Die Struktur
+von dir ist baumartig; im Feld fatherid wird ein Verweis
+auf das Verzeichnis gehalten, in dem das aktuelle Verzeichnis enthalten
+ist. Dessen Wert ist im Startverzeichnis per Definition 0. Über
+den Fremdschlüssel did zeigt jeder Datensatz aus der file-Tabelle
+an, zu welchem Verzeichnis er gehört. Die Tabellen stehen demnach
+in einer 1:n-Beziehung zueinander. Auch die Tabelle dir steht
+in einer 1:n-Beziehung zu sich selbst. Abbildung 42.1
+zeigt ein vereinfachtes E/R-Diagramm des Tabellendesigns.
+
+
+
+Abbildung 42.1: E/R-Diagramm für DirDB
+Das Programm DirDB.java soll folgende
+Anforderungen erfüllen:
+
+Wir implementieren eine Klasse DirDB,
+die (der Einfachheit halber) alle Funktionen mit Hilfe statischer
+Methoden realisiert. Die Klasse und ihre main-Methode
+sehen so aus:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 42 - Datenbankzugriffe mit JDBC
+
+
+
+
+
+42.3 Die DirDB-Beispieldatenbank
+
+
+
+
+
+
+
+
+
+
+42.3.1 Anforderungen und Design
+
+
+
+
+
+
+Name
+Typ
+Bedeutung
+
+did
+INT
+Primärschlüssel
+
+dname
+CHAR(100)
+Verzeichnisname
+
+fatherdid
+INT
+Schlüssel Vaterverzeichnis
+
+entries
+INT
+Anzahl der Verzeichniseinträge
+
+
+
+
+Name
+Typ
+Bedeutung
+
+fid
+INT
+Primärschlüssel
+
+did
+INT
+Zugehöriges Verzeichnis
+
+fname
+CHAR(100)
+Dateiname
+
+fsize
+INT
+Dateigröße
+
+fdate
+DATE
+Änderungsdatum
+
+ftime
+TIME
+Änderungszeit
+
+
+
+
+
+
+
+42.3.2 Das Rahmenprogramm
+
+
+
+
+Listing 42.2: Das Rahmenprogramm der DirDB-Datenbank
+
+
+
+
+
+001 import java.util.*;
+002 import java.io.*;
+003 import java.sql.*;
+004 import java.text.*;
+005 import gk.util.*;
+006
+007 public class DirDB
+008 {
+009 //---Constants-----------------------------------------------
+010 static int INSTANT185 = 1;
+011 static int ACCESS95 = 2;
+012 static int HSQLDB = 3;
+013
+014 //---Pseudo constants----------------------------------------
+015 static String FILESEP = System.getProperty("file.separator");
+016
+017 //---Static Variables----------------------------------------
+018 static int db = INSTANT185;
+019 static Connection con;
+020 static Statement stmt;
+021 static Statement stmt1;
+022 static DatabaseMetaData dmd;
+023 static int nextdid = 1;
+024 static int nextfid = 1;
+025
+026 //---main-------------------------------------------------
+027 public static void main(String[] args)
+028 {
+029 if (args.length < 1) {
+030 System.out.println(
+031 "usage: java DirDB [A|I|H] <command> [<options>]"
+032 );
+033 System.out.println("");
+034 System.out.println("command options");
+035 System.out.println("---------------------------------");
+036 System.out.println("POPULATE <directory>");
+037 System.out.println("COUNT");
+038 System.out.println("FINDFILE <name>");
+039 System.out.println("FINDDIR <name>");
+040 System.out.println("BIGGESTFILES <howmany>");
+041 System.out.println("CLUSTERING <clustersize>");
+042 System.exit(1);
+043 }
+044 if (args[0].equalsIgnoreCase("A")) {
+045 db = ACCESS95;
+046 } else if (args[0].equalsIgnoreCase("H")) {
+047 db = HSQLDB;
+048 }
+049 try {
+050 if (args[1].equalsIgnoreCase("populate")) {
+051 open();
+052 createTables();
+053 populate(args[2]);
+054 close();
+055 } else if (args[1].equalsIgnoreCase("count")) {
+056 open();
+057 countRecords();
+058 close();
+059 } else if (args[1].equalsIgnoreCase("findfile")) {
+060 open();
+061 findFile(args[2]);
+062 close();
+063 } else if (args[1].equalsIgnoreCase("finddir")) {
+064 open();
+065 findDir(args[2]);
+066 close();
+067 } else if (args[1].equalsIgnoreCase("biggestfiles")) {
+068 open();
+069 biggestFiles(Integer.parseInt(args[2]));
+070 close();
+071 } else if (args[1].equalsIgnoreCase("clustering")) {
+072 open();
+073 clustering(Integer.parseInt(args[2]));
+074 close();
+075 }
+076 } catch (SQLException e) {
+077 while (e != null) {
+078 System.err.println(e.toString());
+079 System.err.println("SQL-State: " + e.getSQLState());
+080 System.err.println("ErrorCode: " + e.getErrorCode());
+081 e = e.getNextException();
+082 }
+083 System.exit(1);
+084 } catch (Exception e) {
+085 System.err.println(e.toString());
+086 System.exit(1);
+087 }
+088 }
+089 }
+
+
+In main
+wird zunächst ein usage-Text definiert, der immer dann
+ausgegeben wird, wenn das Programm ohne Argumente gestartet wird.
+Die korrekte Aufrufsyntax ist:
+
+
+java DirDB [A|I|H] <command> [<options>]
+
+
+
+
+Nach dem Programmnamen folgt zunächst der Buchstabe »A«, +»I« oder »H«, um anzugeben, ob die Access-, InstantDB- +oder HSQLDB-Datenbank verwendet werden soll. Das nächste Argument +gibt den Namen des gewünschten Kommandos an. In der folgenden +verschachtelten Verzweigung werden gegebenenfalls weitere Argumente +gelesen und die Methode zum Ausführen des Programms aufgerufen. +Den Abschluss der main-Methode +bildet die Fehlerbehandlung, bei der die Ausnahmen des Typs SQLException +und Exception +getrennt behandelt werden. +
+
![]() |
+
+
+ +Das vollständige Programm findet sich auf der DVD zum Buch unter +dem Namen DirDB.java. In diesem Abschnitt +sind zwar auch alle Teile abgedruckt, sie finden sich jedoch nicht +zusammenhängend wieder, sondern sind über die verschiedenen +Unterabschnitte verteilt. Das importierte Paket gk.util +kann wie in Abschnitt 13.2.3 +beschrieben installiert werden. |
+
+
|
+![]() |
+
+Wie in Listing 42.2 zu sehen +ist, rufen alle Kommandos zunächst die Methode open +zum Öffnen der Datenbank auf. Anschließend führen +sie ihre spezifischen Kommandos aus und rufen dann close +auf, um die Datenbank wieder zu schließen. + +
+Beim Öffnen der Datenbank wird zunächst mit Class.forName +der passende Datenbanktreiber geladen und beim Treibermanager registriert. +Anschließend besorgt das Programm ein Connection-Objekt, +das an die statische Variable con +gebunden wird. An dieser Stelle sind die potentiellen Code-Unterschiede +zwischen den beiden Datenbanken gut zu erkennen: +
+
+
+
+001 /**
+002 * Öffnet die Datenbank.
+003 */
+004 public static void open()
+005 throws Exception
+006 {
+007 //Treiber laden und Connection erzeugen
+008 if (db == INSTANT185) {
+009 Class.forName("jdbc.idbDriver");
+010 con = DriverManager.getConnection(
+011 "jdbc:idb=dirdb.prp",
+012 new Properties()
+013 );
+014 } else if (db == HSQLDB) {
+015 Class.forName("org.hsqldb.jdbcDriver");
+016 con = DriverManager.getConnection(
+017 "jdbc:hsqldb:hsqldbtest",
+018 "SA",
+019 ""
+020 );
+021 } else {
+022 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
+023 con = DriverManager.getConnection("jdbc:odbc:DirDB");
+024 }
+025 //Metadaten ausgeben
+026 dmd = con.getMetaData();
+027 System.out.println("");
+028 System.out.println("Connection URL: " + dmd.getURL());
+029 System.out.println("Driver Name: " + dmd.getDriverName());
+030 System.out.println("Driver Version: " + dmd.getDriverVersion());
+031 System.out.println("");
+032 //Statementobjekte erzeugen
+033 stmt = con.createStatement();
+034 stmt1 = con.createStatement();
+035 }
+036
+037 /**
+038 * Schließt die Datenbank.
+039 */
+040 public static void close()
+041 throws SQLException
+042 {
+043 stmt.close();
+044 stmt1.close();
+045 con.close();
+046 }
+
+ |
+
+Nachdem die Verbindung hergestellt wurde, liefert der Aufruf von getMetaData +ein Objekt des Typs DatabaseMetaData. +Es kann dazu verwendet werden, weitere Informationen über die +Datenbank abzufragen. Wir geben lediglich den Connection-String und +Versionsinformationen zu den geladenen Treibern aus. DatabaseMetaData +besitzt darüber hinaus noch viele weitere Variablen und Methoden, +auf die wir hier nicht näher eingehen wollen. Am Ende von open +erzeugt das Programm zwei Statement-Objekte +stmt und stmt1, +die in den übrigen Methoden zum Ausführen der SQL-Befehle +verwendet werden. Zum Schließen der Datenbank werden zunächst +die beiden Statement-Objekte +und dann die Verbindung selbst geschlossen. + + + + +
+Um tatsächlich eine Verbindung zu einer der drei angegebenen +Datenbanken herstellen zu können, müssen auf Systemebene +die nötigen Voraussetzungen dafür geschaffen werden: +
+Unsere Anwendung geht davon aus, dass die Datenbank bereits angelegt +ist, erstellt die nötigen Tabellen und Indexdateien aber selbst. +Die dazu nötigen SQL-Befehle sind CREATE +TABLE zum Anlegen einer Tabelle und CREATE +INDEX zum Anlegen einer Indexdatei. Diese Befehle werden +mit der Methode executeUpdate +des Statement-Objekts +ausgeführt, denn sie produzieren keine Ergebnismenge, sondern +als DDL-Anweisungen lediglich den Rückgabewert 0. Das Anlegen +der Tabellen erfolgt mit der Methode createTables: + + +
+
+
+
+001 /**
+002 * Legt die Tabellen an.
+003 */
+004 public static void createTables()
+005 throws SQLException
+006 {
+007 //Anlegen der Tabelle dir
+008 try {
+009 stmt.executeUpdate("DROP TABLE dir");
+010 } catch (SQLException e) {
+011 //Nichts zu tun
+012 }
+013 stmt.executeUpdate("CREATE TABLE dir (" +
+014 "did INT," +
+015 "dname CHAR(100)," +
+016 "fatherdid INT," +
+017 "entries INT)"
+018 );
+019 stmt.executeUpdate("CREATE INDEX idir1 ON dir ( did )");
+020 stmt.executeUpdate("CREATE INDEX idir2 ON dir ( fatherdid )");
+021 //Anlegen der Tabelle file
+022 try {
+023 stmt.executeUpdate("DROP TABLE file");
+024 } catch (SQLException e) {
+025 //Nichts zu tun
+026 }
+027 stmt.executeUpdate("CREATE TABLE file (" +
+028 "fid INT ," +
+029 "did INT," +
+030 "fname CHAR(100)," +
+031 "fsize INT," +
+032 "fdate DATE," +
+033 "ftime CHAR(5))"
+034 );
+035 stmt.executeUpdate("CREATE INDEX ifile1 ON file ( fid )");
+036 }
+
+ |
+
+
![]() |
+
+
+ +Um die Tabellen zu löschen, falls sie bereits vorhanden sind, +wird zunächst die Anweisung DROP TABLE +ausgeführt. Sie ist in einem eigenen try-catch-Block +gekapselt, denn manche Datenbanken lösen eine Ausnahme aus, falls +die zu löschenden Tabellen nicht existieren. Diesen »Fehler« +wollen wir natürlich ignorieren und nicht an den Aufrufer weitergeben. |
+
+
|
+![]() |
+
+Nachdem die Tabellen angelegt wurden, können sie mit der Methode +populate gefüllt werden. +populate bekommt dazu vom Rahmenprogramm +den Namen des Startverzeichnisses übergeben, das rekursiv durchlaufen +werden soll, und ruft addDirectory +auf, um das erste Verzeichnis mit Hilfe des Kommandos INSERT +INTO (das ebenfalls an executeUpdate +übergeben wird) in die Tabelle dir einzutragen. Der Code +sieht etwas unleserlich aus, weil einige Stringliterale einschließlich +der zugehörigen einfachen Anführungsstriche übergeben +werden müssen. Sie dienen in SQL-Befehlen als Begrenzungszeichen +von Zeichenketten. + +
+Für das aktuelle Verzeichnis wird dann ein File-Objekt +erzeugt und mit listFiles +(seit dem JDK 1.2 verfügbar) eine Liste der Dateien und Verzeichnisse +in diesem Verzeichnis erstellt. Jede Datei wird mit einem weiteren +INSERT INTO in die file-Tabelle +eingetragen, für jedes Unterverzeichnis ruft addDirectory +sich selbst rekursiv auf. +
+
![]() |
+
+
+ +Am Ende wird mit einem UPDATE-Kommando +die Anzahl der Einträge im aktuellen Verzeichnis in das Feld +entries der Tabelle dir eingetragen. Der Grund für +diese etwas umständliche Vorgehensweise (wir hätten das +auch gleich beim Anlegen des dir-Satzes erledigen können) +liegt darin, dass wir auch ein Beispiel für die Anwendung der +UPDATE-Anweisung geben wollten. |
+
+
|
+![]() |
+
+
+
+
+001 /**
+002 * Durchläuft den Verzeichnisbaum rekursiv und schreibt
+003 * Verzeichnis- und Dateinamen in die Datenbank.
+004 */
+005 public static void populate(String dir)
+006 throws Exception
+007 {
+008 addDirectory(0, "", dir);
+009 }
+010
+011 /**
+012 * Fügt das angegebene Verzeichnis und alle
+013 * Unterverzeichnisse mit allen darin enthaltenen
+014 * Dateien zur Datenbank hinzu.
+015 */
+016 public static void addDirectory(
+017 int fatherdid, String parent, String name
+018 )
+019 throws Exception
+020 {
+021 String dirname = "";
+022 if (parent.length() > 0) {
+023 dirname = parent;
+024 if (!parent.endsWith(FILESEP)) {
+025 dirname += FILESEP;
+026 }
+027 }
+028 dirname += name;
+029 System.out.println("processing " + dirname);
+030 File dir = new File(dirname);
+031 if (!dir.isDirectory()) {
+032 throw new Exception("not a directory: " + dirname);
+033 }
+034 //Verzeichnis anlegen
+035 int did = nextdid++;
+036 stmt.executeUpdate(
+037 "INSERT INTO dir VALUES (" +
+038 did + "," +
+039 "\'" + name + "\'," +
+040 fatherdid + "," +
+041 "0)"
+042 );
+043 //Verzeichniseinträge lesen
+044 File[] entries = dir.listFiles();
+045 //Verzeichnis durchlaufen
+046 for (int i = 0; i < entries.length; ++i) {
+047 if (entries[i].isDirectory()) {
+048 addDirectory(did, dirname, entries[i].getName());
+049 } else {
+050 java.util.Date d = new java.util.Date(
+051 entries[i].lastModified()
+052 );
+053 SimpleDateFormat sdf;
+054 //Datum
+055 sdf = new SimpleDateFormat("yyyy-MM-dd");
+056 String date = sdf.format(d);
+057 //Zeit
+058 sdf = new SimpleDateFormat("HH:mm");
+059 String time = sdf.format(d);
+060 //Satz anhängen
+061 stmt.executeUpdate(
+062 "INSERT INTO file VALUES (" +
+063 (nextfid++) + "," +
+064 did + "," +
+065 "\'" + entries[i].getName() + "\'," +
+066 entries[i].length() + "," +
+067 "{d \'" + date + "\'}," +
+068 "\'" + time + "\')"
+069 );
+070 System.out.println(" " + entries[i].getName());
+071 }
+072 }
+073 //Anzahl der Einträge aktualisieren
+074 stmt.executeUpdate(
+075 "UPDATE dir SET entries = " + entries.length +
+076 " WHERE did = " + did
+077 );
+078 }
+
+ |
+
+
![]() |
+
+
+ +Hier tauchen die beiden im Rahmenprogramm definierten statischen Variablen +nextdid und nextfid +wieder auf. Sie liefern die Primärschlüssel für Datei- +und Verzeichnissätze und werden nach jedem eingefügten Satz +automatisch um eins erhöht. Daß dieses Verfahren nicht +mehrbenutzerfähig ist, leuchtet ein, denn die Zähler werden +lokal zur laufenden Applikation erhöht. Eine bessere Lösung +bieten Primärschlüsselfelder, die beim Einfügen von +der Datenbank einen automatisch hochgezählten eindeutigen +Wert erhalten. Dafür sieht JDBC allerdings kein standardisiertes +Verfahren vor, meist kann ein solches Feld in der INSERT INTO-Anweisung +einfach ausgelassen werden. Alternativ könnten Schlüsselwerte +vor dem Einfügen aus einer zentralen Key-Tabelle geholt und transaktionssicher +hochgezählt werden. |
+
+
|
+![]() |
+
+Das COUNT-Kommando soll die +Anzahl der Verzeichnisse und Dateien zählen, die mit dem POPULATE-Kommando +in die Datei eingefügt wurden. Wir verwenden dazu ein einfaches +SELECT-Kommando, das mit der +COUNT(*)-Option die Anzahl der +Sätze in einer Tabelle zählt: + + +
+
+
+
+001 /**
+002 * Gibt die Anzahl der Dateien und Verzeichnisse aus.
+003 */
+004 public static void countRecords()
+005 throws SQLException
+006 {
+007 ResultSet rs = stmt.executeQuery(
+008 "SELECT count(*) FROM dir"
+009 );
+010 if (!rs.next()) {
+011 throw new SQLException("SELECT COUNT(*): no result");
+012 }
+013 System.out.println("Directories: " + rs.getInt(1));
+014 rs = stmt.executeQuery("SELECT count(*) FROM file");
+015 if (!rs.next()) {
+016 throw new SQLException("SELECT COUNT(*): no result");
+017 }
+018 System.out.println("Files: " + rs.getInt(1));
+019 rs.close();
+020 }
+
+ |
+
+Die SELECT-Befehle werden mit +der Methode executeQuery +an das Statement-Objekt +übergeben, denn wir erwarten nicht nur eine einfache Ganzzahl +als Rückgabewert, sondern eine komplette Ergebnismenge. Die Besonderheit +liegt in diesem Fall darin, dass wegen der Spaltenangabe COUNT(*) +lediglich ein einziger Satz zurückgegeben wird, der auch nur +ein einziges Feld enthält. Auf dieses können wir am einfachsten +über seinen numerischen Index 1 zugreifen und es durch Aufruf +von getInt +gleich in ein int +umwandeln lassen. Das Ergebnis geben wir auf dem Bildschirm aus und +wiederholen anschließend dieselbe Prozedur für die Tabelle +file. + + + + +
+Um eine bestimmte Datei oder Tabelle in unserer Datenbank zu suchen, +verwenden wir ebenfalls ein SELECT-Statement. +Im Gegensatz zu vorher lassen wir uns nun mit der Spaltenangabe »*« +alle Felder der Tabelle geben. Zudem hängen wir an die +Abfrageanweisung eine WHERE-Klausel +an, um eine Suchbedingung formulieren zu können. Mit Hilfe des +LIKE-Operators führen wir +eine Mustersuche durch, bei der die beiden SQL-Wildcards »%« +(eine beliebige Anzahl Zeichen) und »_« (ein einzelnes beliebiges +Zeichen) verwendet werden können. Zusätzlich bekommt InstantDB +die (nicht SQL-92-konforme) Option IGNORE +CASE angehängt, um bei der Suche nicht zwischen Groß- +und Kleinschreibung zu unterscheiden (ist bei Access 7.0 nicht nötig). + +
+Die von executeQuery +zurückgegebene Ergebnismenge wird mit next +Satz für Satz durchlaufen und auf dem Bildschirm ausgegeben. +Mit Hilfe der Methode getDirPath +wird zuvor der zugehörige Verzeichnisname rekonstruiert und vor +dem Dateinamen ausgegeben. Dazu wird in einer Schleife zur angegebenen +did (Verzeichnisschlüssel) +so lange das zugehörige Verzeichnis gesucht, bis dessen fatherdid +0 ist, also das Startverzeichnis erreicht ist. Rückwärts +zusammengebaut und mit Trennzeichen versehen, ergibt diese Namenskette +den kompletten Verzeichnisnamen. +
+
![]() |
+![]() |
+
+
+ +Der in dieser Methode verwendete ResultSet +wurde mit dem zweiten Statement-Objekt +stmt1 erzeugt. Hätten wir +dafür die zu diesem Zeitpunkt noch geöffnete Variable stmt +verwendet, wäre das Verhalten des Programms undefiniert gewesen, +weil die bestehende Ergebnismenge durch das Erzeugen einer neuen Ergebnismenge +auf demselben Statement-Objekt +ungültig geworden wäre. |
+
+
|
+![]() |
+
+
+
+
+001 /**
+002 * Gibt eine Liste aller Files auf dem Bildschirm aus,
+003 * die zu dem angegebenen Dateinamen passen. Darin dürfen
+004 * die üblichen SQL-Wildcards % und _ enthalten sein.
+005 */
+006 public static void findFile(String name)
+007 throws SQLException
+008 {
+009 String query = "SELECT * FROM file " +
+010 "WHERE fname LIKE \'" + name + "\'";
+011 if (db == INSTANT185) {
+012 query += " IGNORE CASE";
+013 }
+014 ResultSet rs = stmt.executeQuery(query);
+015 while (rs.next()) {
+016 String path = getDirPath(rs.getInt("did"));
+017 System.out.println(
+018 path + FILESEP +
+019 rs.getString("fname").trim()
+020 );
+021 }
+022 rs.close();
+023 }
+024
+025 /**
+026 * Liefert den Pfadnamen zu dem Verzeichnis mit dem
+027 * angegebenen Schlüssel.
+028 */
+029 public static String getDirPath(int did)
+030 throws SQLException
+031 {
+032 String ret = "";
+033 while (true) {
+034 ResultSet rs = stmt1.executeQuery(
+035 "SELECT * FROM dir WHERE did = " + did
+036 );
+037 if (!rs.next()) {
+038 throw new SQLException(
+039 "no dir record found with did = " + did
+040 );
+041 }
+042 ret = rs.getString("dname").trim() +
+043 (ret.length() > 0 ? FILESEP + ret : "");
+044 if ((did = rs.getInt("fatherdid")) == 0) {
+045 break;
+046 }
+047 }
+048 return ret;
+049 }
+
+ |
+
+Das DirDB-Programm bietet mit dem Kommando FINDDIR +auch die Möglichkeit, nach Verzeichnisnamen zu suchen. Die Implementierung +dieser Funktion ähnelt der vorigen und wird durch die Methode +findDir realisiert: + + +
+
+
+
+001 /**
+002 * Gibt eine Liste aller Verzeichnisse auf dem Bildschirm
+003 * aus, die zu dem angegebenen Verzeichnisnamen passen.
+004 * Darin dürfen die üblichen SQL-Wildcards % und _
+005 * enthalten sein.
+006 */
+007 public static void findDir(String name)
+008 throws SQLException
+009 {
+010 String query = "SELECT * FROM dir " +
+011 "WHERE dname LIKE \'" + name + "\'";
+012 if (db == INSTANT185) {
+013 query += " IGNORE CASE";
+014 }
+015 ResultSet rs = stmt.executeQuery(query);
+016 while (rs.next()) {
+017 System.out.println(
+018 getDirPath(rs.getInt("did")) +
+019 " (" + rs.getInt("entries") + " entries)"
+020 );
+021 }
+022 rs.close();
+023 }
+
+ |
+
+
![]() |
+
+
+
+Wird das DirDB-Programm von der Kommandozeile aufgerufen, kann es
+unter Umständen schwierig sein, die Wildcards »%« oder
+»_« einzugeben, weil sie vom Betriebssystem oder der Shell
+als Sonderzeichen angesehen werden. Durch Voranstellen des passenden
+Escape-Zeichens (das könnte beispielsweise der Backslash sein)
+kann die Sonderbedeutung aufgehoben werden. In der DOS-Box von Windows
+95 oder NT kann die Sonderbedeutung des »%« nur aufgehoben
+werden, indem das Zeichen »%« doppelt geschrieben wird.
+Soll beispielsweise nach allen Dateien mit der Erweiterung .java
+gesucht werden, so ist DirDb
+unter Windows wie folgt aufzurufen:
+
+
+Weiterhin ist zu beachten, dass die Interpretation der Wildcards von
+den unterschiedlichen Datenbanken leider nicht einheitlich gehandhabt
+wird. Während das obige Kommando unter InstantDB korrekt funktioniert,
+ist bei der Access-Datenbank die Ergebnismenge leer. Der Grund kann
+- je nach verwendeter Version - darin liegen, dass entweder der Stern
+»*« anstelle des »%« als Wildcard erwartet wird
+oder dass die Leerzeichen am Ende des Feldes als signifikant
+angesehen werden und daher auch hinter dem Suchbegriff ein
+Wildcard-Zeichen angegeben werden muss:
+
+ |
+
+
|
+![]() |
+
+Eine ähnliche SELECT-Anweisung +begegnet uns, wenn wir uns die Aufgabe stellen, die howmany +größten Dateien unserer Datenbank anzuzeigen. Hierzu fügen +wir eine ORDER BY-Klausel an +und sortieren die Abfrage absteigend nach der Spalte fsize. +Von der Ergebnismenge geben wir dann die ersten howmany Elemente +aus: + + +
+
+
+
+001 /**
+002 * Gibt die howmany größten Dateien aus.
+003 */
+004 public static void biggestFiles(int howmany)
+005 throws SQLException
+006 {
+007 ResultSet rs = stmt.executeQuery(
+008 "SELECT * FROM file ORDER BY fsize DESC"
+009 );
+010 for (int i = 0; i < howmany; ++i) {
+011 if (rs.next()) {
+012 System.out.print(
+013 getDirPath(rs.getInt("did")) +
+014 FILESEP + rs.getString("fname").trim()
+015 );
+016 System.out.println(
+017 Str.getFormatted("%10d", rs.getInt("fsize"))
+018 );
+019 }
+020 }
+021 rs.close();
+022 }
+
+ |
+
+Bevor wir uns weiterführenden Themen zuwenden, wollen wir uns +eine letzte Anwendung unserer Beispieldatenbank ansehen. Viele Dateisysteme +(allen voran das alte FAT-Dateisystem unter MS-DOS und Windows) speichern +die Dateien in verketteten Zuordnungseinheiten fester Größe, +den Clustern. Ist die Clustergröße +beispielsweise 4096 Byte, so belegt eine Datei auch dann 4 kByte Speicher, +wenn sie nur ein Byte groß ist. Immer, wenn die Größe +einer Datei nicht ein genaues Vielfaches der Clustergröße +ist, bleibt der letzte Cluster unvollständig belegt und wertvoller +Plattenspeicher bleibt ungenutzt. Ist die Clustergröße +hoch, wird vor allem dann viel Platz verschwendet, wenn das Dateisystem +sehr viele kleine Dateien enthält. Die folgende Funktion clustering +berechnet zu einer gegebenen Clustergröße die Summe der +Dateilängen und stellt sie dem tatsächlichen Platzbedarf +aufgrund der geclusterten Speicherung gegenüber: + + +
+
+
+
+001 /**
+002 * Summiert einerseits die tatsächliche Größe aller
+003 * Dateien und andererseits die Größe, die sie durch
+004 * das Clustering mit der angegebenen Clustergröße
+005 * belegen. Zusätzlich wird der durch das Clustering
+006 * "verschwendete" Speicherplatz ausgegeben.
+007 */
+008 public static void clustering(int clustersize)
+009 throws SQLException
+010 {
+011 int truesize = 0;
+012 int clusteredsize = 0;
+013 double wasted;
+014 ResultSet rs = stmt.executeQuery(
+015 "SELECT * FROM file"
+016 );
+017 while (rs.next()) {
+018 int fsize = rs.getInt("fsize");
+019 truesize += fsize;
+020 if (fsize % clustersize == 0) {
+021 clusteredsize += fsize;
+022 } else {
+023 clusteredsize += ((fsize / clustersize) + 1)*clustersize;
+024 }
+025 }
+026 System.out.println("true size = " + truesize);
+027 System.out.println("clustered size = " + clusteredsize);
+028 wasted = 100 * (1 - ((double)truesize / clusteredsize));
+029 System.out.println("wasted space = " + wasted + " %");
+030 }
+
+ |
+
+Um beispielsweise den Einfluss der geclusterten Darstellung bei einer
+Clustergröße von 8192 zu ermitteln, kann das Programm wie
+folgt aufgerufen werden:
+
+
+java DirDB I clustering 8192
+
+
+
+
+Die Ausgabe des Programms könnte dann beispielsweise so aussehen:
+
+
+InstantDB - Version 1.85
+Copyright (c) 1997-1998 Instant Computer Solutions Ltd.
+
+Connection URL: jdbc:idb:dirdb.prp
+Driver Name: InstantDB JDBC Driver
+Driver Version: Version 1.85
+
+true size = 94475195
+clustered size = 112861184
+wasted space = 16.290799323884464 %
+
+
+
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |