1. Wprowadzenie do parsowania strumieniowego
W Javie historycznie ukształtowały się dwa podejścia do pracy z XML.
Wiemy już, że DOM (Document Object Model) buduje w pamięci drzewo dokumentu. Wygodnie jest poruszać się po elementach i je edytować, ale dla dużych plików jest to zbyt kosztowne: pamięć szybko się kończy.
Z kolei SAX (Simple API for XML) przetwarza plik sekwencyjnie i wywołuje zdarzenia przy napotkaniu tagów. To oszczędne pod względem pamięci — można pracować z ogromnymi dokumentami. Jednak pisanie handlerów bywa niewygodne, a powrót do poprzedniej struktury jest niemożliwy.
StAX (Streaming API for XML) pojawił się jako kompromis. Jest również strumieniowy, jak SAX, ale daje programiście większą kontrolę: sami „ciągniemy” zdarzenia ze strumienia, gdy jest to potrzebne. Takie podejście nazywa się parsowaniem pull i pozwala pisać bardziej zrozumiały i elastyczny kod.
Wprowadzenie do StAX
StAX (Streaming API for XML) — to nowoczesny strumieniowy parser XML dla Javy, wprowadzony w JDK 6+.
Główna idea: parsowanie pull („ciągnący” parser).
W przeciwieństwie do SAX, gdzie parser sam wywołuje twoje metody (model push), w StAX to ty zarządzasz procesem:
Sam prosisz parser: „Daj mi następne zdarzenie!”
Analogicznie:
SAX — to jak telewizja: zdarzenia „płyną” do ciebie, musisz reagować.
StAX — to jak serwis, w którym sam naciskasz „następne wideo”, kiedy jesteś gotów.
Kluczowe klasy StAX
Aby pracować ze StAX, potrzebne są dwie główne klasy z pakietu javax.xml.stream:
- XMLInputFactory — fabryka do tworzenia parserów.
- XMLStreamReader — sam parser strumieniowy, który czyta XML „po kawałkach”.
Przykład podstawowego kodu:
import javax.xml.stream.*;
import java.io.FileInputStream;
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("data.xml"));
while (reader.hasNext()) {
int event = reader.next();
// obsługa zdarzenia
}
reader.close();
2. Zasada działania StAX: model pull
W StAX to ty sam zarządzasz odczytem XML:
- Otwierasz strumień (np. plik).
- Tworzysz XMLStreamReader.
- W pętli wywołujesz reader.next(), aby pobrać następne zdarzenie.
- Sprawdzasz typ zdarzenia (START_ELEMENT, END_ELEMENT, CHARACTERS, itd.).
- Gdy dojdziesz do odpowiedniego miejsca — przetwarzasz dane.
- Zamykasz parser.
Schemat działania:
flowchart TD
A[Otwórz XMLStreamReader] --> B{hasNext?}
B -- tak --> C["next()"]
C --> D{Jaki typ zdarzenia?}
D -- START_ELEMENT --> E[Obsługa początku elementu]
D -- CHARACTERS --> F[Obsługa tekstu]
D -- END_ELEMENT --> G[Obsługa końca elementu]
D -- END_DOCUMENT --> H[Zakończ]
B -- nie --> H
Dlaczego to wygodne?
- Sam decydujesz, kiedy czytać kolejny element.
- Można „zatrzymać się” w potrzebnym miejscu i przetworzyć tylko część pliku.
- Nie trzeba pisać mnóstwa handlerów, jak w SAX.
3. Typy zdarzeń w StAX
Kiedy wywołujesz reader.next(), parser zwraca typ zdarzenia — liczbę całkowitą (stała z interfejsu XMLStreamConstants). Oto podstawowe typy zdarzeń:
- START_ELEMENT — początek elementu XML (<tag>).
- END_ELEMENT — koniec elementu XML (</tag>).
- CHARACTERS — treść tekstowa między tagami.
- END_DOCUMENT — koniec dokumentu.
Przykład obsługi zdarzeń:
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
String name = reader.getLocalName();
System.out.println("Początek elementu: " + name);
break;
case XMLStreamConstants.CHARACTERS:
String text = reader.getText().trim();
if (!text.isEmpty()) {
System.out.println("Tekst: " + text);
}
break;
case XMLStreamConstants.END_ELEMENT:
System.out.println("Koniec elementu: " + reader.getLocalName());
break;
}
}
4. Przydatne uwagi
Kiedy używać StAX?
StAX to idealny wybór, jeśli:
- Plik XML jest bardzo duży (gigabajty) i nie chcesz ładować go w całości do pamięci.
- Trzeba przetworzyć tylko część dokumentu (np. znaleźć określony element i się zatrzymać).
- Potrzebny jest prosty i zrozumiały kod: StAX jest prostszy niż SAX i nie wymaga pisania mnóstwa handlerów.
Przykładowe zadania:
- Import dużego pliku XML z danymi (np. eksport z 1C, wyciągi bankowe, katalogi produktów).
- Wyszukiwanie i przetwarzanie tylko potrzebnych elementów (np. jedynie <transaction> z miliona wpisów).
- Przekształcanie XML „w locie” (np. filtrowanie, agregacja).
Porównanie DOM, SAX i StAX
| Podejście | Pamięć | Prostota | Elastyczność | Kiedy używać |
|---|---|---|---|---|
| DOM | Duża (wszystko w pamięci) | Bardzo prosty | Można modyfikować drzewo | Małe/średnie pliki, gdy trzeba edytować XML |
| SAX | Minimalna | Trudny (obsługa zdarzeń) | Tylko odczyt, brak możliwości cofania | Bardzo duże pliki, prosta obróbka |
| StAX | Minimalna | Średnia (model pull) | Można czytać partiami, łatwo przerywać | Duże pliki, gdy potrzebna jest elastyczność i prostota |
StAX — złoty środek:
— Nie zużywa tyle pamięci co DOM.
— Nie wymaga skomplikowanych procedur obsługi zdarzeń, jak SAX.
— Pozwala kontrolować proces parsowania.
5. Przykład: odczyt dużego pliku XML za pomocą StAX
Załóżmy, że mamy plik "books.xml":
<library>
<book>
<title>Java dla początkujących</title>
<author>Ivan Ivanov</author>
</book>
<book>
<title>Zaawansowana Java</title>
<author>Pyotr Petrov</author>
</book>
<!-- ... wiele książek ... -->
</library>
Zadanie: wypisać wszystkie tytuły książek.
Kod w StAX:
import javax.xml.stream.*;
import java.io.*;
public class StaxDemo {
public static void main(String[] args) throws Exception {
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("books.xml"));
while (reader.hasNext()) {
int event = reader.next();
if (event == XMLStreamConstants.START_ELEMENT && "title".equals(reader.getLocalName())) {
reader.next(); // przechodzimy do CHARACTERS
System.out.println("Książka: " + reader.getText());
}
}
reader.close();
}
}
Zalety:
- Nie ładujemy całego pliku do pamięci.
- Można przetworzyć nawet milion książek — program się nie zawiesi.
6. Typowe błędy podczas pracy ze StAX
Błąd nr 1: zapomniano zamknąć parser lub strumień. Zawsze zamykaj XMLStreamReader i strumień (InputStream), aby nie dochodziło do wycieków zasobów.
Błąd nr 2: brak sprawdzenia typu zdarzenia. Nie wszystkie zdarzenia to początek lub koniec elementu. Sprawdzaj typ zdarzenia, w przeciwnym razie możesz uzyskać puste ciągi lub pominąć potrzebne dane.
Błąd nr 3: nieuwzględnienie zagnieżdżenia elementów. Jeśli struktura XML jest złożona (np. książki wewnątrz sekcji), kontroluj bieżący poziom zagnieżdżenia, aby nie pomylić elementów.
Błąd nr 4: używanie DOM do dużych plików. Jeśli plik jest duży — nie używaj DOM, w przeciwnym razie możesz otrzymać OutOfMemoryError. Dla dużych plików — tylko StAX lub SAX.
Błąd nr 5: brak obsługi wyjątków. Praca z plikami i XML może zgłaszać wyjątki (XMLStreamException, IOException). Obsługuj je lub propaguj wyżej.
GO TO FULL VERSION