1. DOM (Document Object Model)
Manchmal ist die XML-Datei zu groß, um sie vollständig in den Speicher zu laden. Es kommt auch vor, dass die Struktur des Dokuments im Voraus unbekannt oder zu komplex ist. In anderen Fällen müssen nur einzelne Teile der Daten verarbeitet werden, nicht die gesamte Datei, oder das Dokument soll direkt zur Laufzeit geändert werden: einen Knoten löschen, ein neues Element hinzufügen oder einen Teil der Struktur umorganisieren.
In solchen Situationen eignen sich die bewährten Werkzeuge – DOM und SAX. Sie erlauben es, direkt mit dem Inhalt von XML zu arbeiten, ohne an vorab beschriebene Java-Klassen gebunden zu sein.
Wie DOM funktioniert
DOM ist eine Möglichkeit, ein XML-Dokument als Objektbaum im Speicher darzustellen. Jeder Tag wird zu einem Baumknoten (Node), und Attribute, Textwerte und sogar Kommentare sind eigene Objekte. Nach dem Laden des Dokuments erhalten Sie vollen Zugriff auf dessen Struktur: Sie können Elemente und Attribute lesen, ändern, löschen und hinzufügen.
Wesentliche DOM-Klassen in Java
- DocumentBuilderFactory – Fabrik zum Erzeugen eines Parsers.
- DocumentBuilder – Parser, der XML in einen Baum verwandelt.
- Document – Wurzelobjekt des Baums.
- Element – XML-Element (Tag).
- NodeList – Liste von Knoten (z. B. alle <item> innerhalb von <items>).
Beispiel: Eine XML-Datei mit DOM lesen
Nehmen wir an, wir müssen folgende XML-Datei lesen:
<contacts>
<person id="1">
<name>Ivan</name>
<email>ivan@example.com</email>
</person>
<person id="2">
<name>Mariya</name>
<email>maria@example.com</email>
</person>
</contacts>
Code: Elemente lesen und durchlaufen
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.File;
public class DomExample {
public static void main(String[] args) throws Exception {
// 1. Fabrik und Parser erstellen
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// 2. XML-Datei in den Speicher laden
Document doc = builder.parse(new File("contacts.xml"));
// 3. Wurzelelement ermitteln
Element root = doc.getDocumentElement();
System.out.println("Wurzelelement: " + root.getTagName());
// 4. Liste aller <person> ermitteln
NodeList persons = root.getElementsByTagName("person");
for (int i = 0; i < persons.getLength(); i++) {
Element person = (Element) persons.item(i);
String id = person.getAttribute("id");
String name = person.getElementsByTagName("name").item(0).getTextContent();
String email = person.getElementsByTagName("email").item(0).getTextContent();
System.out.println("id: " + id + ", name: " + name + ", email: " + email);
}
}
}
Was hier passiert:
- Zuerst erstellen wir den Parser und laden die XML-Datei.
- Wir greifen auf das Wurzelelement (contacts) zu.
- Wir finden alle Elemente <person> und iterieren darüber.
- Für jedes <person> lesen wir das Attribut id sowie die Elemente <name> und <email>.
Baumskizze (DOM) für das Beispiel
contacts
├── person (id="1")
│ ├── name ("Ivan")
│ └── email ("ivan@example.com")
└── person (id="2")
├── name ("Mariya")
└── email ("maria@example.com")
XML mit DOM ändern
DOM erlaubt nicht nur das Lesen, sondern auch das Ändern von XML. Fügen wir z. B. eine neue Person hinzu:
// Neues Element <person> erstellen
Element newPerson = doc.createElement("person");
newPerson.setAttribute("id", "3");
// <name>
Element name = doc.createElement("name");
name.setTextContent("Sergey");
newPerson.appendChild(name);
// <email>
Element email = doc.createElement("email");
email.setTextContent("sergey@example.com");
newPerson.appendChild(email);
// Zum Wurzelknoten hinzufügen
root.appendChild(newPerson);
// Änderungen in Datei speichern
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(new File("contacts-updated.xml")));
Wichtigste Vor- und Nachteile von DOM
- Vorteile: praktisch für kleine Dateien, beliebige Änderungen leicht möglich, im Baum in jede Richtung navigierbar.
- Nachteile: das gesamte XML wird im Speicher gehalten. Für große Dateien (Hunderte MB und mehr) – ungeeignet, der Speicher ist schnell erschöpft.
2. SAX (Simple API for XML)
SAX ist ein ereignisgesteuerter Parser. Er baut keinen Baum auf, sondern liest XML „von links nach rechts“ und ruft Ereignishandler auf: „Element begonnen“, „Textinhalt“, „Element beendet“ usw. Sie schreiben Ihren eigenen Handler und reagieren auf die für Sie relevanten Ereignisse.
Analogie:
Wenn DOM so ist, als würden Sie alle Seiten eines Organizers auf dem Tisch ausbreiten, dann ist SAX so, als würden Sie den Organizer Seite für Seite lesen und nur dann Notizen machen, wenn Sie die benötigte Seite finden.
Wesentliche SAX-Klassen in Java
- SAXParserFactory, SAXParser – Fabrik und Parser.
- DefaultHandler – Basisklasse für die Ereignisverarbeitung.
- Handler-Methoden: startElement, characters, endElement, startDocument, endDocument.
Beispiel: Eine XML-Datei mit SAX lesen
Nehmen wir dieselbe Datei contacts.xml. Wir möchten einfach die Namen und E‑Mail-Adressen aller Personen ausgeben.
Code: SAX-Parser
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import java.io.File;
public class SaxExample {
public static void main(String[] args) throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(new File("contacts.xml"), new ContactHandler());
}
}
class ContactHandler extends DefaultHandler {
private String currentElement = "";
private String name = "";
private String email = "";
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
currentElement = qName;
if ("person".equals(qName)) {
String id = attributes.getValue("id");
System.out.println("Neue Person, id: " + id);
}
}
@Override
public void characters(char[] ch, int start, int length) {
String text = new String(ch, start, length).trim();
if (text.isEmpty()) return;
if ("name".equals(currentElement)) {
name = text;
} else if ("email".equals(currentElement)) {
email = text;
}
}
@Override
public void endElement(String uri, String localName, String qName) {
if ("person".equals(qName)) {
System.out.println("Name: " + name + ", email: " + email);
name = "";
email = "";
}
currentElement = "";
}
}
Schauen wir uns nun in einfachen Worten an, was hier passiert. Wenn der SAX-Parser auf einen öffnenden Tag trifft, z. B. <person>, ruft er die Methode startElement auf. Dort lesen wir sofort das Attribut id und geben es aus. Wenn im Inneren Text erscheint – zum Beispiel ein Name oder eine E‑Mail –, landet die Ausführung in der Methode characters, wo wir diesen Text in temporären Variablen speichern. Und wenn der Parser zum schließenden Tag </person> kommt, wird endElement aufgerufen. Zu diesem Zeitpunkt kennen wir bereits den Namen und die E‑Mail der Person und können sie ausgeben. Danach werden die Variablen geleert, um für den nächsten Kontakt bereit zu sein.
Die Idee ist, dass SAX nicht das gesamte XML im Speicher hält, sondern als strombasierter „Leser“ arbeitet: Es geht die Datei von oben nach unten durch und reagiert auf Ereignisse – Tag-Beginn, Text, Tag-Ende. Das ist schnell und speichersparend, besonders für große Dateien.
Kurzvergleich DOM und SAX
| DOM | SAX | |
|---|---|---|
| Stil | Baum (gesamte Struktur im Speicher) | Ereignisse (Verarbeitung „on the fly“) |
| Speicher | Lädt das gesamte XML | Minimaler Speicherverbrauch |
| Änderungen | Lesen/ändern/hinzufügen möglich | Nur Lesen (in der Regel) |
| Datensuche | Überall leicht zu durchsuchen | Man muss „wissen, wo man sich befindet“ |
| Dateigröße | Für kleine und mittlere Dateien | Für sehr große Dateien |
3. Wann DOM und wann SAX verwenden
DOM ist ideal, wenn:
- die Datei klein bis mittelgroß ist.
- Sie wiederholt auf verschiedene Teile des XML zugreifen müssen.
- die Struktur des Dokuments geändert werden soll.
SAX – Ihre Wahl, wenn:
- die Datei riesig ist und nicht in den Speicher geladen werden kann.
- Sie schnell nur einen Teil der Informationen „herausziehen“ möchten (z. B. alle <item>-Elemente mit einem bestimmten Attribut finden).
- hohe Performance und minimaler Speicherverbrauch erforderlich sind.
- Sie das XML nicht ändern, sondern nur lesen wollen.
Tipp:
In realen Projekten werden oft beide Ansätze kombiniert: DOM – für „menschliche“ Einstellungen und kleine Konfigurationen, SAX – für die Verarbeitung von Logs, Exports, großen Importen.
4. Praxisaufgabe: ein kleiner Parser für Ihre Anwendung
In Ihrer Übungsanwendung (z. B. „Kontaktbuch“) ist die Aufgabe entstanden, schnell die Anzahl der Benutzer mit einer E‑Mail bei gmail.com zu ermitteln.
DOM-Lösung:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("contacts.xml"));
NodeList emails = doc.getElementsByTagName("email");
int count = 0;
for (int i = 0; i < emails.getLength(); i++) {
String email = emails.item(i).getTextContent();
if (email.endsWith("@gmail.com")) {
count++;
}
}
System.out.println("Benutzer mit gmail.com: " + count);
SAX-Lösung:
class GmailCounterHandler extends DefaultHandler {
private String currentElement = "";
int count = 0;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
currentElement = qName;
}
@Override
public void characters(char[] ch, int start, int length) {
if ("email".equals(currentElement)) {
String email = new String(ch, start, length).trim();
if (email.endsWith("@gmail.com")) {
count++;
}
}
}
@Override
public void endDocument() {
System.out.println("Benutzer mit gmail.com: " + count);
}
}
5. Besonderheiten und Feinheiten bei der Arbeit mit DOM und SAX
- DOM kann den gesamten Speicher „auffressen“, wenn die XML-Datei sehr groß ist. Wenn Sie plötzlich einen OutOfMemoryError sehen, ist es wahrscheinlich Zeit, auf SAX umzusteigen.
- SAX erfordert Aufmerksamkeit: Sie müssen verfolgen, in welchem Element Sie sich befinden, und die benötigten Daten sorgfältig sammeln. Manchmal braucht man einen Stack oder zusätzliche Variablen, um sich in stark verschachtelten Strukturen nicht zu verirren.
- In SAX kann die Methode characters mehrmals für denselben Textblock aufgerufen werden (insbesondere wenn der Text lang ist oder Sonderzeichen enthält). Besser den Text in einem StringBuilder puffern.
- DOM eignet sich hervorragend für Suche, Navigation und Strukturänderungen, aber nicht für die Stream-Verarbeitung.
- Wenn Sie nicht sicher sind, welchen Ansatz Sie wählen sollen – beginnen Sie der Einfachheit halber mit DOM; wenn es speichertechnisch „schwer“ wird, stellen Sie auf SAX um.
6. Typische Fehler bei der Arbeit mit DOM und SAX
Fehler Nr. 1: Falsche Behandlung von Leerzeichen und Zeilenumbrüchen in SAX.
Die Methode characters kann Textstücke zurückgeben, einschließlich Leerzeichen und Zeilenumbrüche zwischen Elementen. Wenn man nicht auf .trim().isEmpty() filtert, erhält man viele „leere“ Aufrufe oder setzt den Text fehlerhaft zusammen.
Fehler Nr. 2: Versuch, XML mit SAX zu ändern.
SAX ist nur zum Lesen da! Wenn Sie die Struktur ändern müssen – verwenden Sie DOM.
Fehler Nr. 3: Ereignisreihenfolge in SAX durcheinandergebracht.
Wenn Variablen nicht in endElement zurückgesetzt werden, kann es zu „Daten-Lecks“ zwischen Elementen kommen.
Fehler Nr. 4: DOM für riesige Dateien verwenden.
Das Ergebnis – OutOfMemoryError oder sehr langsame Ausführung.
Fehler Nr. 5: Falsches Casting in DOM.
In DOM ist alles ein Node, aber um mit Attributen und untergeordneten Elementen zu arbeiten, muss auf Element gecastet werden. Ein Fehler beim Casting kann zu einer ClassCastException führen.
GO TO FULL VERSION