JavaRush /Java 博客 /Random-ZH /Java 程序员的 XML 基础知识。第 3.2 部分(共 3 部分)- DOM
Ярослав
第 40 级
Днепр

Java 程序员的 XML 基础知识。第 3.2 部分(共 3 部分)- DOM

已在 Random-ZH 群组中发布
<h2>简介</h2>本文的所有读者大家好,这一部分专门介绍 DOM。下一篇将专门讨论 JAXB,至此,XML 基础知识的循环就完成了。首先会有一些理论,然后只有实践。让我们开始吧。 <h2>DOM(文档对象模型)- 理论</h2>DOM 处理程序的设计方式是一次读取所有 XML 并保存它,以树的形式创建一个层次结构,我们可以在其中轻松移动并访问我们需要的元素。 因此,给定一个到顶部元素的链接,我们可以获取到其内部元素的所有链接。此外,元素内部的元素是该元素的子元素,而它是它们的父元素。将所有 XML 读入内存后,我们将简单地浏览其结构并执行我们需要的操作。关于 Java 中 DOM 的编程部分的一些信息:DOM 有许多为描述不同数据而创建的接口。所有这些接口都继承了一个公共接口——Node。因为,事实上,DOM 中最常见的数据类型是 Node,它可以是任何东西。 每个节点都有以下用于检索信息的有用方法:
  1. getNodeName– 获取主机名。
  2. getNodeValue– 获取节点值。
  3. getNodeType– 获取节点类型。
  4. getParentNode– 获取给定节点所在的节点。
  5. getChildNodes– 获取所有派生节点(给定节点内的节点)。
  6. getAttributes– 获取所有节点属性。
  7. getOwnerDocument– 获取该节点的文档。
  8. getFirstChild/getLastChild– 获取第一个/最后一个派生节点。
  9. getLocalName– 在处理命名空间以获取没有前缀的名称时很有用。
  10. getTextContent– 返回元素内的所有文本以及给定元素内的所有元素,包括换行符和空格。
注意方法 9:除非您在 DocumentFactory 中使用 setNamespaceAware(true) 方法来触发命名空间处理,否则它将始终返回 null。 现在,一个重要的细节:这些方法对于所有节点都是通用的,但是在节点中我们可以同时拥有元素和属性。这里有一些问题:一个元素可以有什么价值?属性可以有哪些派生节点?而其他的则不一致。一切都非常简单:每个方法都将根据 Node type 来工作。当然,使用逻辑就足够了,以免混淆。例如:属性能够具有哪些属性?该元素还有什么其他含义?但是,为了不亲自尝试所有内容,在官方文档中,有一个非常有用的表格,说明每种方法如何根据节点类型工作: 质量结果很差,因此这里是文档的链接(表格位于页面顶部):节点文档 最重要的是要记住:
  1. 只有元素才有属性。
  2. 元素没有任何意义。
  3. 元素节点的名称与标签的名称相同,属性节点的名称与属性的名称相同。
<h2>DOM(文档对象模型)- 实践</h2>在实践部分,我们将分析在 XML 中搜索信息的各种类型的任务。我们还拿了上一篇文章中的两个任务来比较便利性。让我们开始吧,最好从导入开始:
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
我提供导入,这样您就不会混淆这些类:) 任务 1 - 我们需要获取有关所有员工的信息并将其从以下 XML 文件输出到控制台:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee name="Maksim" job="Middle Software Developer" />
                <employee name="Ivan" job="Junior Software Developer" />
                <employee name="Franklin" job="Junior Software Developer" />
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee name="Herald" job="Middle Software Developer" />
                <employee name="Adam" job="Middle Software Developer" />
                <employee name="Leroy" job="Junior Software Developer" />
            </employees>
        </office>
    </offices>
</company>
正如我们所看到的,我们将所有信息存储在员工元素中。为了将其存储在程序中的某个位置,让我们创建一个类Employee
public class Employee {
    private String name, job;

    public Employee(String name, String job) {
        this.name = name;
        this.job = job;
    }

    public String getName() {
        return name;
    }

    public String getJob() {
        return job;
    }
}
现在我们已经描述了存储数据的结构,我们需要一个存储员工的集合。我们将在代码本身中创建它。我们还需要基于 XML 创建一个文档:
public class DOMExample {
    // Список для сотрудников из XML file
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Получение фабрики, чтобы после получить билдер documentов.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Получor из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
        DocumentBuilder builder = factory.newDocumentBuilder();

        // Запарсor XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Howим нам нужно.
        Document document = builder.parse(new File("resource/xml_file1.xml"));
    }
}
一旦我们收到文档,我们就对 XML 文件的整个结构拥有无限的权力。我们可以随时获取任何元素,返回检查任何数据,总的来说,这是比 SAX 更灵活的方法。在此任务的上下文中,我们只需要提取所有员工元素,然后提取有关他们的所有信息。这很简单:
public class DOMExample {
    // Список для сотрудников из XML file
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Получение фабрики, чтобы после получить билдер documentов.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Получor из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
        DocumentBuilder builder = factory.newDocumentBuilder();

        // Запарсor XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Howим нам нужно.
        Document document = builder.parse(new File("resource/xml_file1.xml"));

        // Получение списка всех элементов employee внутри корневого element (getDocumentElement возвращает ROOT элемент XML file).
        NodeList employeeElements = document.getDocumentElement().getElementsByTagName("employee");

        // Перебор всех элементов employee
        for (int i = 0; i < employeeElements.getLength(); i++) {
            Node employee = employeeElements.item(i);

            // Получение атрибутов каждого element
            NamedNodeMap attributes = employee.getAttributes();

            // Добавление сотрудника. Атрибут - тоже Node, потому нам нужно получить meaning атрибута с помощью метода getNodeValue()
            employees.add(new Employee(attributes.getNamedItem("name").getNodeValue(), attributes.getNamedItem("job").getNodeValue()));
        }

        // Вывод информации о каждом сотруднике
        for (Employee employee : employees)
            System.out.println(String.format("Информации о сотруднике: Name - %s, должность - %s.", employee.getName(), employee.getJob()));
    }
}
该解决方案的描述在解决方案中是正确的。建议在查看代码后返回理论并再次阅读。事实上,一切都本能地很清楚。仔细阅读评论,应该不会有任何问题,如果有任何问题,你可以写在评论中,我会回答,或者写在链接中,或者直接运行你的IDEA并尝试自己玩代码,如果你还没有这样做。 因此,运行代码后我们得到以下输出:
Информации о сотруднике: Name - Maksim, должность - Middle Software Developer.
Информации о сотруднике: Name - Ivan, должность - Junior Software Developer.
Информации о сотруднике: Name - Franklin, должность - Junior Software Developer.
Информации о сотруднике: Name - Herald, должность - Middle Software Developer.
Информации о сотруднике: Name - Adam, должность - Middle Software Developer.
Информации о сотруднике: Name - Leroy, должность - Junior Software Developer.
可以看到,任务已经顺利完成了!让我们继续执行下一个任务 :) 任务 2 - 从控制台输入元素的名称,您需要从以下 XML 文件中显示有关其内部所有元素及其属性的信息:
<?xml version="1.0" encoding="UTF-8"?>
<root>
    <oracle>
        <connection value="jdbc:oracle:thin:@10.220.140.48:1521:test1" />
        <user value="secretOracleUsername" />
        <password value="111" />
    </oracle>

    <mysql>
        <connection value="jdbc:mysql:thin:@10.220.140.48:1521:test1" />
        <user value="secretMySQLUsername" />
        <password value="222" />
    </mysql>
</root>
一切都很简单:我们必须通过元素的名称来获取元素,然后对元素进行计数,然后遍历所有子节点。为此,您需要迭代所有属于元素的子节点的所有子节点。 此问题的解决方案:
public class DOMExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Ридер для считывания имени тега из консоли
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        // Получение фабрики, чтобы после получить билдер documentов.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Получor из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
        DocumentBuilder builder = factory.newDocumentBuilder();

        // Запарсor XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Howим нам нужно.
        Document document = builder.parse(new File("resource/xml_file3.xml"));

        // Считывание имени тега для поиска его в файле
        String element = reader.readLine();

        // Получение списка элементов, однако для удобства будем рассматривать только первое совпадение в documentе.
        // Так же заметьте, что мы ищем элемент внутри documentа, а не рут element. Это сделано для того, чтобы рут элемент тоже искался.
        NodeList matchedElementsList = document.getElementsByTagName(element);

        // Даже если element нет, всегда будет возвращаться список, просто он будет пустым.
        // Потому, чтобы утверждать, что element нет в файле, достаточно проверить размер списка.
        if (matchedElementsList.getLength() == 0) {
            System.out.println("Tag " + element + " не был найден в файле.");
        } else {
            // Получение первого element.
            Node foundedElement = matchedElementsList.item(0);

            System.out.println("Элемент был найден!");

            // Если есть данные внутри, вызов метода для вывода всей информации
            if (foundedElement.hasChildNodes())
                printInfoAboutAllChildNodes(foundedElement.getChildNodes());
        }
    }

    /**
     * Рекурсивный метод, который будет выводить информацию про все узлы внутри всех узлов, которые пришли параметром, пока не будут перебраны все узлы.
     * @param list Список узлов.
     */
    private static void printInfoAboutAllChildNodes(NodeList list) {
        for (int i = 0; i < list.getLength(); i++) {
            Node node = list.item(i);

            // У элементов есть два вида узлов - другие элементы or текстовая информация. Потому нужно разбираться две ситуации отдельно.
            if (node.getNodeType() == Node.TEXT_NODE) {
                // Фильтрация информации, так How пробелы и переносы строчек нам не нужны. Это не информация.
                String textInformation = node.getNodeValue().replace("\n", "").trim();

                if(!textInformation.isEmpty())
                    System.out.println("Внутри element найден текст: " + node.getNodeValue());
            }
            // Если это не текст, а элемент, то обрабатываем его How элемент.
            else {
                System.out.println("Найден элемент: " + node.getNodeName() + ", его атрибуты:");

                // Получение атрибутов
                NamedNodeMap attributes = node.getAttributes();

                // Вывод информации про все атрибуты
                for (int k = 0; k < attributes.getLength(); k++)
                    System.out.println("Name атрибута: " + attributes.item(k).getNodeName() + ", его meaning: " + attributes.item(k).getNodeValue());
            }

            // Если у данного element еще остались узлы, то вывести всю информацию про все его узлы.
            if (node.hasChildNodes())
                printInfoAboutAllChildNodes(node.getChildNodes());
        }
    }
}
解决方案的完整描述都在评论中,但我想使用理论图片中的示例以图形方式说明我们使用的方法。 我们假设我们需要显示有关 html 标签的信息。正如你所看到的,我们需要从树的根部开始从上到下。所有的线都是节点。 在解决方案中,我们将从所需元素的开头递归地遍历其所有节点,如果其中一个节点是一个元素,那么我们也会迭代该元素的所有节点。 因此,运行代码后,我们得到了根元素的以下输出:
Элемент был найден!
Найден элемент: oracle, его атрибуты:
Найден элемент: connection, его атрибуты:
Name атрибута: value, его meaning: jdbc:oracle:thin:@10.220.140.48:1521:test1
Найден элемент: user, его атрибуты:
Name атрибута: value, его meaning: secretOracleUsername
Найден элемент: password, его атрибуты:
Name атрибута: value, его meaning: 111
Найден элемент: mysql, его атрибуты:
Найден элемент: connection, его атрибуты:
Name атрибута: value, его meaning: jdbc:mysql:thin:@10.220.140.48:1521:test1
Найден элемент: user, его атрибуты:
Name атрибута: value, его meaning: secretMySQLUsername
Найден элемент: password, его атрибуты:
Name атрибута: value, его meaning: 222
问题已经成功解决了! 任务 3 – 从以下 XML 文件中,保存有关学生、教授和员工的信息,您需要读取这些信息并将其输出到控制台:
<?xml version="1.0" encoding="UTF-8"?>
<database>
    <students>
        <student name="Maksim" course="3" specialization="CE" />
        <student name="Stephan" course="1" specialization="CS" />
        <student name="Irvin" course="2" specialization="CE" />
    </students>

    <professors>
        <professor name="Herald" experience="7 years in University" discipline="Math" />
        <professor name="Adam" experience="4 years in University" discipline="Programming" />
        <professor name="Anton" experience="6 years in University" discipline="English" />
    </professors>

    <service>
        <member name="John" position="janitor" />
        <member name="Jordan" position="janitor" />
        <member name="Mike" position="janitor" />
    </service>
</database>
这个任务很简单,但是很有趣。首先,我们需要创建 4 个类:员工、教授和学生,以及一个公共抽象类 Human,以便将每个类的 name 变量置于一个共同的分母下: 抽象父类
public abstract class Human {
    private String name;

    public Human(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
学生
public class Student extends Human {
    private String course, specialization;

    public Student(String name, String course, String specialization) {
        super(name);
        this.course = course;
        this.specialization = specialization;
    }

    public String getCourse() {
        return course;
    }

    public String getSpecialization() {
        return specialization;
    }

    public String toString() {
        return "Голодный студент " + getName() + " " + course + "-го курса, обучающийся по специальности " + specialization;
    }
}
教授
public class Professor extends Human {
    private String experience, discipline;

    public Professor(String name, String experience, String discipline) {
        super(name);
        this.experience = experience;
        this.discipline = discipline;
    }

    public String getExperience() {
        return experience;
    }

    public String getDiscipline() {
        return discipline;
    }

    public String toString() {
        return "Профессор " + getName() + ", обладающий опытом: \"" + experience + "\", выкладает дисциплину " + discipline;
    }
}
员工
public class Member extends Human {
    private String position;

    public Member(String name, String position) {
        super(name);
        this.position = position;
    }

    public String getPosition() {
        return position;
    }

    public String toString() {
        return "Сотрудник обслуживающего персонала " + getName() + ", должность: " + position;
    }
}
现在我们的类已经准备好了,我们只需要编写代码来获取所有元素 Student、Professor 和 Member,然后获取它们的属性。对于存储,我们将使用一个集合来存储所有人共有的父类的对象 - Human。 所以,这个问题的解决方案:
public class DOMExample {
    // Коллекция для хранения всех людей
    private static ArrayList<Human> humans = new ArrayList<>();

    // Константы для элементов
    private static final String PROFESSOR = "professor";
    private static final String MEMBER = "member";
    private static final String STUDENT = "student";

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Получение фабрики, чтобы после получить билдер documentов.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Получor из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
        DocumentBuilder builder = factory.newDocumentBuilder();

        // Запарсor XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Howим нам нужно.
        Document document = builder.parse(new File("resource/xml_file3.xml"));

        // Получение информации про каждый элемент отдельно
        collectInformation(document, PROFESSOR);
        collectInformation(document, MEMBER);
        collectInformation(document, STUDENT);

        // Вывод информации
        humans.forEach(System.out::println);
    }

    /**
     * Метод ищет информацию про теги по имени element и вносит всю информацию в коллекцию humans.
     * @param document Документ, в котором будем искать элементы.
     * @param element Name element, теги которого нужно найти. Должна быть одна из констант, которые определяются выше.
     */
    private static void collectInformation(Document document, final String element) {
        // Получение всех элементов по имени тега.
        NodeList elements = document.getElementsByTagName(element);

        // Перебор всех найденных элементов
        for (int i = 0; i < elements.getLength(); i++) {
            // Получение всех атрибутов element
            NamedNodeMap attributes = elements.item(i).getAttributes();
            String name = attributes.getNamedItem("name").getNodeValue();

            // В зависимости от типа element, нам нужно собрать свою дополнительну информацию про каждый подкласс, а после добавить нужные образцы в коллекцию.
            switch (element) {
                case PROFESSOR: {
                    String experience = attributes.getNamedItem("experience").getNodeValue();
                    String discipline = attributes.getNamedItem("discipline").getNodeValue();

                    humans.add(new Professor(name, experience, discipline));
                } break;
                case STUDENT: {
                    String course = attributes.getNamedItem("course").getNodeValue();
                    String specialization = attributes.getNamedItem("specialization").getNodeValue();

                    humans.add(new Student(name, course, specialization));
                } break;
                case MEMBER: {
                    String position = attributes.getNamedItem("position").getNodeValue();

                    humans.add(new Member(name, position));
                } break;
            }
        }
    }
}
请注意,我们只需要元素名称即可从文档中获取所有这些元素。这极大地简化了查找所需信息的过程。有关代码的所有信息都包含在注释中。没有使用以前任务中未出现的任何新内容。 代码输出:
Профессор Herald, обладающий опытом: "7 years in University", выкладает дисциплину Math
Профессор Adam, обладающий опытом: "4 years in University", выкладает дисциплину Programming
Профессор Anton, обладающий опытом: "6 years in University", выкладает дисциплину English
Сотрудник обслуживающего персонала John, должность: janitor
Сотрудник обслуживающего персонала Jordan, должность: janitor
Сотрудник обслуживающего персонала Mike, должность: janitor
Голодный студент Maksim 3-го курса, обучающийся по специальности CE
Голодный студент Stephan 1-го курса, обучающийся по специальности CS
Голодный студент Irvin 2-го курса, обучающийся по специальности CE
问题解决了! 建议何时使用 DOM、何时使用 SAX 这些工具之间的区别在于功能和速度。如果您需要更灵活的功能并且可以承受程序性能的浪费,那么您的选择是 DOM,但如果您的主要目标是减少内存成本,那么 DOM 不是最佳选择,因为它从 XML 文件中读取所有信息,并且存储它。因此,SAX顺序读取方法的成本较低。 简而言之:如果您需要性能 - SAX,功能 - DOM。 <h2>结论</h2>每个程序员都有自己的工具,并且根据任务的不同,您需要使用某些工具。在有关 SAX 和 DOM 的文章中,我的目标是教您如何从 XML 文件中提取信息并按照您需要的方式处理它们。然而,即使您阅读了这些文章,您也不能声称已经学会了如何使用这些工具。您应该练习、测试文章中的代码、了解其工作原理,并尝试自己编写一些东西。毕竟,最重要的是练习。最后一篇文章将在未来几天(显然是在比赛结束后)发布,并将专门针对 JAXB。JAXB 是一个以 XML 格式保存程序中对象的工具。就这些了,我希望本文对您有所帮助,祝您编程顺利 :) 上一篇文章:[竞赛] Java 程序员的 XML 基础知识 - 第 3.1 部分(共 3 部分)- SAX
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION