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:
- Ein neues Objekt der Klasse
Properties
erzeugen - Über
properties.setProperty(key, value)
die Schlüssel und zugehörigen Werte setzen - 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ürzburg
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"?-->
<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”