1. DOM (Document Object Model)
Иногда XML-файл слишком большой, чтобы загрузить его целиком в память. Бывает и так, что структура документа заранее неизвестна или слишком сложна. В других случаях нужно обработать только отдельные части данных, а не весь файл, или же прямо во время работы программы изменить документ: удалить узел, добавить новый элемент или перестроить часть структуры.
В таких ситуациях подходят старые и проверенные инструменты — DOM и SAX. Они позволяют напрямую работать с содержимым XML без привязки к заранее описанным Java-классам.
Как работает DOM
DOM — это способ представить XML-документ в виде дерева объектов в памяти. Каждый тег становится узлом дерева (Node), а атрибуты, текстовые значения и даже комментарии — отдельными объектами. После загрузки документа вы получаете полный доступ к его структуре: можно читать, изменять, удалять, добавлять элементы и атрибуты.
Основные классы DOM в Java
- DocumentBuilderFactory — фабрика для создания парсера.
- DocumentBuilder — парсер, который превращает XML в дерево.
- Document — корневой объект дерева.
- Element — элемент XML (тег).
- NodeList — список узлов (например, все <item> внутри <items>).
Пример: Чтение XML-файла с помощью DOM
Допустим, нам нужно прочитать такой XML-файл:
<contacts>
<person id="1">
<name>Иван</name>
<email>ivan@example.com</email>
</person>
<person id="2">
<name>Мария</name>
<email>maria@example.com</email>
</person>
</contacts>
Код: Чтение и обход элементов
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.File;
public class DomExample {
public static void main(String[] args) throws Exception {
// 1. Создаём фабрику и парсер
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// 2. Загружаем XML-файл в память
Document doc = builder.parse(new File("contacts.xml"));
// 3. Получаем корневой элемент
Element root = doc.getDocumentElement();
System.out.println("Корневой элемент: " + root.getTagName());
// 4. Получаем список всех <person>
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);
}
}
}
Что тут происходит:
- Сначала создаём парсер и загружаем XML-файл.
- Получаем корневой элемент (contacts).
- Находим все элементы <person>, перебираем их.
- Для каждого <person> читаем атрибут id, элементы <name> и <email>.
Схема DOM-дерева для примера
contacts
├── person (id="1")
│ ├── name ("Иван")
│ └── email ("ivan@example.com")
└── person (id="2")
├── name ("Мария")
└── email ("maria@example.com")
Изменение XML с помощью DOM
DOM позволяет не только читать, но и менять XML. Например, добавим нового человека:
// Создаём новый элемент <person>
Element newPerson = doc.createElement("person");
newPerson.setAttribute("id", "3");
// <name>
Element name = doc.createElement("name");
name.setTextContent("Сергей");
newPerson.appendChild(name);
// <email>
Element email = doc.createElement("email");
email.setTextContent("sergey@example.com");
newPerson.appendChild(email);
// Добавляем к корню
root.appendChild(newPerson);
// Сохраняем изменения в файл
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(new File("contacts-updated.xml")));
Основные плюсы и минусы DOM
- Плюсы: удобно для небольших файлов, легко делать любые изменения, можно «гулять» по дереву в любом направлении.
- Минусы: весь XML хранится в памяти. Для больших файлов (сотни МБ и больше) — не вариант, быстро закончится память.
2. SAX (Simple API for XML)
SAX — это событийный парсер. Он не строит дерево, а просто читает XML «слева направо» и вызывает обработчики событий: «начался элемент», «текстовое содержимое», «закончился элемент» и т.д. Вы пишете свой обработчик и реагируете на нужные вам события.
Аналогия:
Если DOM — это как разложить все страницы органайзера на столе, то SAX — это будто читать органайзер постранично и делать заметки только тогда, когда встретите нужную страницу.
Основные классы SAX в Java
- SAXParserFactory, SAXParser — фабрика и парсер.
- DefaultHandler — базовый класс для обработки событий.
- Методы-обработчики: startElement, characters, endElement, startDocument, endDocument.
Пример: Чтение XML-файла с помощью SAX
Допустим, тот же файл contacts.xml. Мы хотим просто вывести имена и e-mail всех людей.
Код: SAX-парсер
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("Новый человек, 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 + ", email: " + email);
name = "";
email = "";
}
currentElement = "";
}
}
Теперь разберёмся, что здесь происходит простыми словами. Когда SAX-парсер встречает открывающий тег, например <person>, он вызывает метод startElement. Там мы сразу читаем атрибут id и выводим его на экран. Когда внутри встречается текст — например, имя или e-mail, — управление попадает в метод characters, где мы сохраняем этот текст во временные переменные. А когда парсер доходит до закрывающего тега </person>, вызывается endElement. В этот момент мы уже знаем имя и e-mail человека и можем их распечатать. После этого переменные очищаются, чтобы быть готовыми к следующему контакту.
Идея в том, что SAX не хранит весь XML целиком в памяти, а работает как потоковый «чтец»: проходит файл сверху вниз и реагирует на события — начало тега, текст, конец тега. Это быстро и экономно по памяти, особенно для больших файлов.
Краткое сравнение DOM и SAX
| DOM | SAX | |
|---|---|---|
| Стиль | Дерево (вся структура в памяти) | События (обработка «на лету») |
| Память | Загружает весь XML | Минимальное использование памяти |
| Изменения | Можно читать/изменять/добавлять | Только чтение (обычно) |
| Поиск данных | Легко искать в любом месте | Нужно отслеживать «где ты сейчас» |
| Размер файла | Для маленьких и средних файлов | Для очень больших файлов |
3. Когда использовать DOM, а когда SAX
DOM отлично подходит, если:
- Файл небольшой или средний по размеру.
- Нужно многократно обращаться к разным частям XML.
- Требуется изменять структуру документа.
SAX — ваш выбор, если:
- Файл огромный, и загрузить его в память нельзя.
- Нужно быстро «вытащить» только часть информации (например, найти все элементы <item> с определённым атрибутом).
- Требуется высокая производительность и минимальное использование памяти.
- Вы не планируете изменять XML, только читать.
Совет:
В реальных проектах часто используют оба подхода: DOM — для «человеческих» настроек и небольших конфигов, SAX — для обработки логов, выгрузок, больших импортов.
4. Практическое задание: маленький парсер для приложения
В вашем учебном приложении (например, «контактная книга») допустим, появилась задача: быстро подсчитать количество пользователей с e-mail на gmail.com.
DOM-решение:
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("Пользователей с gmail.com: " + count);
SAX-решение:
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("Пользователей с gmail.com: " + count);
}
}
5. Особенности и нюансы работы с DOM и SAX
- DOM может «подъесть» всю память, если XML-файл очень большой. Если вы вдруг увидите ошибку OutOfMemoryError, скорее всего, пора переходить на SAX.
- SAX требует хорошей внимательности: нужно отслеживать, в каком элементе вы находитесь, и аккуратно собирать нужные данные. Иногда приходится использовать стек или дополнительные переменные, чтобы не запутаться в глубоко вложенных структурах.
- В SAX метод characters может вызываться несколько раз для одного и того же текстового блока (особенно если текст длинный или содержит спецсимволы). Лучше накапливать текст в StringBuilder.
- DOM отлично подходит для поиска, навигации и изменения структуры, но не для потоковой обработки.
- Если вы не уверены, какой подход выбрать — начните с DOM для простоты, а если станет «тяжело» по памяти — перепишите на SAX.
6. Типичные ошибки при работе с DOM и SAX
Ошибка №1: Неправильная обработка пробелов и переносов строк в SAX.
Метод characters может возвращать кусочки текста, в том числе пробелы и переносы строк между элементами. Если не фильтровать .trim().isEmpty(), можно получить много «пустых» вызовов или некорректно собрать текст.
Ошибка №2: Попытка изменить XML с помощью SAX.
SAX — это только для чтения! Если нужно менять структуру — используйте DOM.
Ошибка №3: Нарушение порядка событий в SAX.
Если переменные не сбрасываются в endElement, можно получить «протечку» данных между элементами.
Ошибка №4: Использование DOM для огромных файлов.
Результат — OutOfMemoryError или очень медленная работа.
Ошибка №5: Неправильное приведение типов в DOM.
В DOM всё — это Node, но для работы с атрибутами и дочерними элементами нужно приводить к Element. Ошибка в приведении может привести к ClassCastException.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ