JavaRush /Java Blog /Random-TW /Java 程式設計師的 XML 基礎知識。第 3.2 部分(共 3 部分)- DOM
Ярослав
等級 40
Днепр

Java 程式設計師的 XML 基礎知識。第 3.2 部分(共 3 部分)- DOM

在 Random-TW 群組發布
<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