09.09 Standardisiertes Speichern

In den letzten Kapiteln haben Sie gelernt, wie man Daten speichert. Jetzt stellt sich noch die Frage, wo und in welcher Form Anwendungsdaten (Daten, die der Benutzer anlegen und laden kann, sollten natürlich an einem frei wählbarem Ort in einer passenden Form hinterlegt werden) gespeichert werden sollten. Für den Ort bieten sich relative Pfade oder besser Preferences an. Als Form lernen Sie in diesem Kapitel Properties kennen. Daneben gibt es noch XML und Datenbanken (ggf. mit Persistence-Schicht) als häufige Speicherformen. Diesen werden aber zu einem späteren Zeitpunkt jeweils ein komplettes Kapitel gewidmet.

Properties

Properties stellen eine sehr einfache Art und Weise dar, um Daten zu speichern. Es werden Schlüssel-Werte-Paare (ähnlich wie bei einer Map) in eine Datei geschrieben bzw. gelesen. Eine solche Properties-Datei könnte bspw. so aussehen:

firstname=Stefan
lastname=Kiesel
birthday=14.12.1987
birthplace=Würzburg

Selbstverständlich könnten Sie eine solche Datei auch mit Ihren bereits erworbenen Kenntnissen problemlos auslesen. Java bietet hierfür jedoch Standards – die Klasse java.util.Properties. Sie ermöglichst auf einfachstem Weg das Anlegen und Auslesen einer solchen Datei.

Zuerst legen Sie eine Properties-Datei wie folgt an:

  1. Ein neues Objekt der Klasse Properties erzeugen
  2. Über properties.setProperty(key, value) die Schlüssel und zugehörigen Werte setzen
  3. Die Properties-Datei speichern
// Properties erzeugen
Properties props = new Properties();
props.setProperty("firstname", "Stefan");
props.setProperty("lastname", "Kiesel");
props.setProperty("birthday", "14.12.1987");
props.setProperty("birthplace", "Würzburg");

Beim Speichern haben Sie nun mehrere Möglichkeiten. Zum Einen können Sie den Inhalt in der vorgestellten Form über die Methode store abspeichern. Hierzu übergeben Sie dieser Methode das gewünschte Ziel in Form eines OutputStreams oder eines Writers gefolgt von einem Kommentar, der diese Datei beschreibt (oder null, wenn kein Kommentar erstellt werden soll). Ein Kommentar wird in der Properties-Datei mit einer Raute (#) eingeleitet.

props.store(new FileOutputStream("C:/props.properties"), "comment");

Dateiinhalt:

#comment
#Tue Jun 30 07:40:52 CEST 2009
birthplace=W\u00FCrzburg
birthday=14.12.1987
lastname=Kiesel
firstname=Stefan

Zusätzlich zu Ihrem Kommentar kommt ein Timestamp mit in die Datei. Dieser zeigt an, wann die Datei geschrieben wurde.

Alternativ zu dieser klassischen Form können Sie die Datei auch als XML speichern lassen. Hierzu verwenden Sie die Methode storeToXML. Diese erwartet ebenfalls einen OutputStream, einen Kommentar und optional die gewünschte Zeichenkodierung.

props.storeToXML(new FileOutputStream("C:/props.xml"), "comment");

Dateiinhalt:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
  <comment>comment</comment>
  <entry key="birthplace">Würzburg</entry>
  <entry key="birthday">14.12.1987</entry>
  <entry key="lastname">Kiesel</entry>
  <entry key="firstname">Stefan</entry>
</properties>

XMLs werden (wie bereits in der Einleitung erwähnt) noch einmal ausführlich zu einem späteren Zeitpunkt besprochen.

Um eine Properties-Datei auszulesen, verwenden Sie die Methode load mit einem InputStream bzw. Reader oder (falls es sich um ein XML-Properties-File handelt) die Methode loadFromXML mit einem InputStream. Anschließend können Sie über die get-Methode und den jeweiligen Schlüssel auf die Werte zugreifen.

Properties props = new Properties();
try {
  props.load(new FileInputStream("C:/props.properties"));
}
catch (FileNotFoundException e) {
  e.printStackTrace();
}
catch (IOException e) {
  e.printStackTrace();
}
System.out.println(props.getProperty("firstname")); // Stefan

Folgende Methoden könnten für Sie beim Umgang mit der Properties Klasse auch noch von Nutzen sein:

// Überprüft, ob ein Schlüssel vorhanden ist
System.out.println(props.containsKey("firstname")); // true
System.out.println(props.containsKey("vorname")); // false
// Überprüft, ob ein Wert vorhanden ist
System.out.println(props.containsValue("Stefan")); // true
System.out.println(props.containsValue("Kai")); // false
// Alle Schlüssel als Enumeration
// siehe auch "props.stringPropertyNames();"
Enumeration<?> keys = props.propertyNames();
while (keys.hasMoreElements()) {
  // Ausgabe der Schlüssel
  System.out.println(keys.nextElement());
}
// Alle Werte als Collection
Collection<Object> values = props.values();
for (Object obj : values) {
  // Ausgabe der Werte
  System.out.println(obj);
}

Der geeignete Speicherort

Aus diesem und den letzten Kapiteln kennen Sie viele Möglichkeiten Dateien abzuspeichern. In Form von Properties, Objektserialisierung, Texte, Binäre Daten, in einem eigenen Dateiformat, … Doch wo sollten Sie Ihre Daten speichern?

Relative Pfade

Im Kapitel 09.02 Die Sicht auf das Dateisystem – java.io.File haben Sie relative Pfade kennengelernt. Mit diesen lassen sich Daten relativ zum Ausführungsort (und nicht zum Verzeichnis, in welchem sich Ihr Programm befindet) lesen und schreiben. Das bedeutet aber auch, dass bei jedem Programmstart das selbe Ausführungsverzeichnis garantieren sein muss. Ansonsten können die Daten nicht mehr gefunden werden. Zur Veranschaulichung legen Sie sich am Besten ein äußerst simples Programm an, z. B.:

import java.io.File;

public class Test {

  public static void main(String[] args) {
    System.out.println(new File("test.txt").exists());
  }
}

Dieses kleine Programm überprüft, ob sich im Ausführungsverzeichnis (relativer Pfad) eine Datei mit dem Namen test.txt befindet. Packen Sie hieraus ein ausführbares JAR. Anschließend erstellen Sie in einem beliebigen Verzeichnis (in diesem Beispiel C:\temp) die Datei test.txt und verschieben dorthin auch die eben erstellte JAR-Datei. Führen Sie das JAR nun gewöhnlich aus.

C:\>cd temp
C:\temp>java -jar Test.jar
true

Alles funktioniert wie erwartet. Starten Sie jedoch das JAR aus einem anderen Verzeichnis, in welchem keine test.txt Datei liegt (in diesem Beispiel C:\), erhalten Sie eine andere Ausgabe:

C:\temp>cd ..
C:\>java -jar temp/Test.jar
false

Es wird also eine andere Lösung benötigt.

Das Verzeichnis der JAR-Datei/Klasse auslesen

Es gibt in Java eine Möglichkeit nicht nur an das Ausführungsverzeichnis zu gelangen, sondern auch an den Ort, an dem sich die JAR/Klasse befindet. Da dies aber alles andere als trivial ist, möchte ich an dieser Stelle nicht näher darauf eingehen, sondern auf ein fortgeschrittenes Kapitel verweisen.

Standardverzeichnisse

Sie können Ihre Daten auch in Standardverzeichnissen ablegen. Hierbei hilft Ihnen die Klasse System mit der Methode getProperty(String). Übergeben Sie dieser den String "user.home", erhalten Sie das Verzeichnis des aktuell angemeldeten Nutzers zurück.

System.out.println(System.getProperty("user.home"));

Eine mögliche Ausgabe unter einem Windows-System könnte bspw. so aussehen:

C:\Dokumente und Einstellungen\stkiese

Wenn es etwas spezifischer sein soll, können Sie auch Umgebungsvariablen abrufen. Hierzu dient die Methode getenv(String) der Klasse System. So können Sie unter Windows bspw. das Windows-Installationsverzeichnis abfragen:

System.out.println(System.getenv("windir"));

Am Besten überprüfen Sie aber zuvor, ob es sich auch wirklich um ein Windows-System handelt. Hierzu können Sie wieder die bereits bekannte getProperty(String) Methode verwenden. Diesmal allerdings mit dem Parameter "os.name".

if (System.getProperty("os.name").toLowerCase().contains("windows")) {
  System.out.println(System.getenv("windir")); // bspw. C:\WINNT
}

Alle verfügbaren Properties und Umgebungsvariablen werden mit folgendem Code ausgelesen:

String tmp = null;
Properties sysprops = System.getProperties();
System.out.println("System Properties:");
System.out.println();
Enumeration<?> names = sysprops.propertyNames();
while (names.hasMoreElements()) {
  tmp = names.nextElement().toString();
  System.out.println(tmp + "=" + sysprops.getProperty(tmp));
}
System.out.println();
System.out.println("-------");
System.out.println("Umgebungsvariablen:");
System.out.println();
Map<String, String> env = System.getenv();
for (String str : env.keySet()) {
  System.out.println(str + "=" + System.getenv(str));
}

Preferences

Die eleganteste Möglichkeit stellen jedoch Preferences dar. Mit Ihnen können systemweite oder userbezogene Daten auf einfachste Weise geschrieben und geladen werden – ohne, dass Sie sich um das Verzeichnis kümmern müssen.

Preferences sys = Preferences.systemRoot(); // systemweit
Preferences user = Preferences.userRoot(); // userspezifisch

Über die Put-Methoden können Sie nun einfach Daten (assoziiert mit einem String als Schlüssel) abspeichern.

Preferences prefs = Preferences.systemRoot();
prefs.putBoolean("aBoolValue", true);
prefs.put("aStringValue", "Das ist ein Text!");

Über die Get-Methoden und dem jeweiligen Schlüssel können die Werte dann wieder abgefragt werden. Zusätzlich wird noch ein Default-Wert erwartet. Dieser wird zurückgegeben, falls der Schlüssel nicht gefunden werden kann.

Preferences prefs = Preferences.systemRoot();
System.out.println(prefs.get("aStringValue", null)); // Das ist ein Text!
System.out.println(prefs.getBoolean("aBoolValue", false)); // true
System.out.println(prefs.get("stringValue", "Nicht gefunden")); // Nicht gefunden

Wer eine genauere Unterteilung möchte, kann auch noch zusätzliche Nodes (Unterverzeichnisse) anlegen, in die er die Daten dann speichert. Hierzu wird einfach die Methode node(String) eines Preferences-Objekt mit dem gewünschten Namen aufgerufen.

Preferences prefs = Preferences.systemRoot();
Preferences sub = prefs.node("sub");
prefs.putBoolean("aBoolValue", true);
sub.put("aStringValue", "Das ist ein Text!");
prefs.put("aStringValue", "Ein anderer Text");
System.out.println(prefs.getBoolean("aBoolValue", false)); // true
System.out.println(prefs.get("aStringValue", null)); // Ein anderer Text
System.out.println(sub.get("aStringValue", null)); // Das ist ein Text
System.out.println(sub.get("stringValue", "Nicht gefunden")); // Nicht gefunden

Komplexe Strukturen in Preferences ablegen

Auch wenn es zuerst nicht so aussieht, aber auch über Preferences können ganze Objekte serialisiert und deserialisiert werden. Hierzu verwenden Sie die ObjectStreams gekoppelt mit ByteStreams und speichern/lesen das Resultat über ein byte-Array ab/aus.

public class SerializableObject implements Serializable {

  private static final long serialVersionUID = 1944524442338701776L;

  private String name;


  public SerializableObject(String name) {
    this.name = name;
  }

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }
}
SerializableObject so = new SerializableObject("Serialisierbar");
Preferences prefs = Preferences.systemRoot();

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(so);
prefs.putByteArray("SerializableObject", baos.toByteArray());

ByteArrayInputStream bais = new ByteArrayInputStream(prefs.getByteArray("SerializableObject", new byte[0]));
ObjectInputStream ois = new ObjectInputStream(bais);
SerializableObject loaded = (SerializableObject)ois.readObject();
System.out.println(loaded.getName()); // Serialisierbar

Die gerade kennengelernten Properties können Sie ebenfalls mit Preferences kombinieren. Verwenden Sie zum Lesen bzw. Schreiben der Properties einen java.util.StringReader bzw. java.util.StringWriter. Diese Reader und Writer speichern bzw. lesen den Inhalt in einem/aus einem String.

Preferences prefs = Preferences.userRoot();

// anlegen
Properties props = new Properties();
props.put("firstname", "Stefan");
props.put("lastname", "Kiesel");

// speichern
StringWriter sw = new StringWriter();
props.store(sw, null);
prefs.put("properties", sw.toString());

// auslesen
props.load(new StringReader(prefs.get("properties", null)));
System.out.println(props.get("firstname")); // Stefan

One Reply to “09.09 Standardisiertes Speichern”

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.