JavaRush /Курсы /JAVA 25 SELF /Работа с DOM, SAX: парсинг XML

Работа с DOM, SAX: парсинг XML

JAVA 25 SELF
47 уровень , 1 лекция
Открыта

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.

1
Задача
JAVA 25 SELF, 47 уровень, 1 лекция
Недоступна
Опознание Главного Раздела Библиотеки 🏛️
Опознание Главного Раздела Библиотеки 🏛️
1
Задача
JAVA 25 SELF, 47 уровень, 1 лекция
Недоступна
Список Персонажей или Пользователей в Системе 👤
Список Персонажей или Пользователей в Системе 👤
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ