1. Làm quen với phân tích dạng luồng
Trong Java, theo lịch sử có hai cách tiếp cận để làm việc với XML.
Chúng ta đã biết DOM (Document Object Model) xây dựng cây tài liệu trong bộ nhớ. Việc di chuyển giữa các phần tử và chỉnh sửa chúng rất thuận tiện, nhưng với các tệp lớn thì quá tốn kém: bộ nhớ nhanh chóng cạn kiệt.
Trong khi đó SAX (Simple API for XML) xử lý tệp tuần tự và phát sinh sự kiện khi gặp thẻ. Cách này tiết kiệm bộ nhớ, có thể làm việc với tài liệu khổng lồ. Nhưng viết handler thì bất tiện, và không thể quay lui trong cấu trúc.
StAX (Streaming API for XML) xuất hiện như một sự thỏa hiệp. Nó cũng là dạng luồng như SAX, nhưng cho lập trình viên nhiều quyền kiểm soát hơn: chúng ta tự “kéo” các sự kiện từ luồng khi cần. Cách tiếp cận này được gọi là phân tích kiểu pull, cho phép viết mã rõ ràng và linh hoạt hơn.
Giới thiệu về StAX
StAX (Streaming API for XML) là trình phân tích XML dạng luồng hiện đại cho Java, xuất hiện từ JDK 6+.
Ý tưởng chính: phân tích kiểu pull (trình phân tích “kéo”).
Khác với SAX, nơi parser tự gọi các phương thức của bạn (mô hình push), trong StAX bạn tự điều khiển quy trình:
Bạn tự yêu cầu parser: “Hãy cho tôi sự kiện tiếp theo!”
So sánh dễ hiểu:
SAX — như truyền hình: sự kiện “tuôn” tới bạn, bạn phải phản ứng.
StAX — như một dịch vụ nơi bạn tự bấm “video tiếp theo” khi đã sẵn sàng.
Các lớp chủ chốt của StAX
Để làm việc với StAX, bạn cần hai lớp chính từ gói javax.xml.stream:
- XMLInputFactory — factory để tạo parser.
- XMLStreamReader — chính là parser dạng luồng, đọc XML “từng mẩu”.
Ví dụ mã cơ bản:
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();
// xử lý sự kiện
}
reader.close();
2. Nguyên lý hoạt động của StAX: mô hình pull
Trong StAX bạn tự điều khiển việc đọc XML:
- Mở luồng (ví dụ: tệp).
- Tạo XMLStreamReader.
- Trong vòng lặp gọi reader.next() để lấy sự kiện tiếp theo.
- Kiểm tra loại sự kiện (START_ELEMENT, END_ELEMENT, CHARACTERS, v.v.).
- Khi đến chỗ cần — xử lý dữ liệu.
- Đóng parser.
Sơ đồ hoạt động:
flowchart TD
A[Mở XMLStreamReader] --> B{hasNext?}
B -- có --> C["next()"]
C --> D{Loại sự kiện?}
D -- START_ELEMENT --> E[Xử lý bắt đầu phần tử]
D -- CHARACTERS --> F[Xử lý văn bản]
D -- END_ELEMENT --> G[Xử lý kết thúc phần tử]
D -- END_DOCUMENT --> H[Kết thúc]
B -- không --> H
Có gì tiện lợi?
- Bạn tự quyết định khi nào đọc phần tử tiếp theo.
- Có thể “dừng lại” ở chỗ cần, chỉ xử lý một phần tệp.
- Không cần viết cả đống handler như trong SAX.
3. Các loại sự kiện trong StAX
Khi bạn gọi reader.next(), parser trả về loại sự kiện — một số nguyên (hằng số từ interface XMLStreamConstants). Đây là các loại chính:
- START_ELEMENT — bắt đầu phần tử XML (<tag>).
- END_ELEMENT — kết thúc phần tử XML (</tag>).
- CHARACTERS — nội dung văn bản giữa các thẻ.
- END_DOCUMENT — kết thúc tài liệu.
Ví dụ xử lý sự kiện:
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
String name = reader.getLocalName();
System.out.println("Bắt đầu phần tử: " + name);
break;
case XMLStreamConstants.CHARACTERS:
String text = reader.getText().trim();
if (!text.isEmpty()) {
System.out.println("Văn bản: " + text);
}
break;
case XMLStreamConstants.END_ELEMENT:
System.out.println("Kết thúc phần tử: " + reader.getLocalName());
break;
}
}
4. Những lưu ý hữu ích
Khi nào nên dùng StAX?
StAX là lựa chọn lý tưởng nếu:
- Tệp XML rất lớn (hàng gigabyte), và bạn không muốn nạp toàn bộ vào bộ nhớ.
- Cần xử lý chỉ một phần tài liệu (ví dụ, tìm phần tử nhất định rồi dừng).
- Cần mã đơn giản, dễ hiểu: StAX dễ hơn SAX và không đòi hỏi viết nhiều handler.
Ví dụ bài toán:
- Nhập một tệp XML lớn chứa dữ liệu (ví dụ, xuất từ 1C, sao kê ngân hàng, catalog sản phẩm).
- Tìm và xử lý chỉ những phần tử cần thiết (ví dụ, chỉ <transaction> trong hàng triệu bản ghi).
- Biến đổi XML “ngay trong khi đọc” (ví dụ, lọc, tổng hợp).
So sánh DOM, SAX và StAX
| Cách tiếp cận | Bộ nhớ | Độ đơn giản | Tính linh hoạt | Khi nào dùng |
|---|---|---|---|---|
| DOM | Cao (mọi thứ trong bộ nhớ) | Rất đơn giản | Có thể sửa đổi cây | Tệp nhỏ/vừa, khi cần chỉnh sửa XML |
| SAX | Tối thiểu | Khó (các handler sự kiện) | Chỉ đọc, không thể quay lui | Tệp rất lớn, xử lý đơn giản |
| StAX | Tối thiểu | Trung bình (mô hình pull) | Có thể đọc theo phần, dễ dừng tại chỗ | Tệp lớn, khi cần linh hoạt và đơn giản |
StAX — điểm cân bằng vàng:
— Không tốn bộ nhớ như DOM.
— Không cần handler phức tạp như SAX.
— Cho phép kiểm soát quá trình phân tích.
5. Ví dụ: đọc tệp XML lớn bằng StAX
Giả sử chúng ta có tệp "books.xml":
<library>
<book>
<title>Java dlya nachinayushchikh</title>
<author>Ivan Ivanov</author>
</book>
<book>
<title>Prodvinutyy Java</title>
<author>Pyotr Petrov</author>
</book>
<!-- ... rất nhiều sách ... -->
</library>
Nhiệm vụ: in ra tất cả tên sách.
Mã 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(); // chuyển sang CHARACTERS
System.out.println("Sách: " + reader.getText());
}
}
reader.close();
}
}
Ưu điểm:
- Không tải toàn bộ tệp vào bộ nhớ.
- Có thể xử lý cả triệu cuốn sách — chương trình sẽ không “sập”.
6. Các lỗi thường gặp khi làm việc với StAX
Lỗi #1: quên đóng parser hoặc luồng. Luôn đóng XMLStreamReader và luồng (InputStream) để tránh rò rỉ tài nguyên.
Lỗi #2: không kiểm tra loại sự kiện. Không phải sự kiện nào cũng là bắt đầu hay kết thúc phần tử. Hãy kiểm tra loại sự kiện, nếu không có thể nhận chuỗi rỗng hoặc bỏ lỡ dữ liệu cần thiết.
Lỗi #3: không tính đến mức lồng nhau của phần tử. Nếu cấu trúc XML phức tạp (ví dụ, sách nằm trong các mục), hãy theo dõi mức lồng nhau hiện tại để không nhầm lẫn phần tử.
Lỗi #4: dùng DOM cho tệp lớn. Nếu tệp lớn — đừng dùng DOM, nếu không sẽ gặp OutOfMemoryError. Với tệp lớn — chỉ nên dùng StAX hoặc SAX.
Lỗi #5: không xử lý ngoại lệ. Làm việc với tệp và XML có thể ném ngoại lệ (XMLStreamException, IOException). Hãy xử lý chúng hoặc ném tiếp lên trên.
GO TO FULL VERSION