國產化PDF處理控件Spire.PDF教程:如何在 Java 中通過模板生成 PDF

在企業級應用開發中,生成 PDF 文檔是一項非常常見的需求。無論是發票、報告、合同,還是其他業務文檔,開發人員通常都需要一種高效、穩定的方式來創建 PDF。與其逐行繪制 PDF 內容,不如直接利用?模板?——常見的模板形式包括?HTML 模板?和?PDF 模板?,開發者只需將動態數據填充進去,就能快速生成所需文檔。

E-iceblue旗下Spire系列產品,是文檔處理領域的佼佼者,支持國產化信創在本文中,我們將介紹如何使用?Spire.PDF for Java?通過模板生成 PDF 文件。文章不僅涵蓋 HTML 模板和 PDF 模板的使用方法,還會提供一些高效生成文檔的最佳實踐和常見問題解答,幫助你在實際項目中更好地應用。

Spire.PDF for Java免費試用下載?

Spire.PDF for Java 簡介

Spire.PDF for Java是一個功能強大的 PDF 庫,提供用于創建、讀取、編輯和轉換 PDF 文件的完整 API。它支持以下功能:

  • 從零生成 PDF?:可以從頭開始創建全新的 PDF 文檔。
  • 將 HTML?、圖片和文本轉換為 PDF?:支持將網頁內容、圖片或文本內容快速生成 PDF。
  • 修改或編輯現有 PDF?:可以對已有 PDF 文件進行內容更新或調整。
  • 操作文本、圖片、表格和注釋?:提供豐富的文檔處理能力,滿足多樣化需求。

安裝:

  1. 從官網下載 Spire.PDF for Java,并將 JAR 文件添加到項目的構建路徑中。
  2. 如果你使用?Maven?管理項目,可以在 pom.xml 中添加如下依賴:
<repositories><repository><id>com.e-iceblue</id><name>e-iceblue</name><url>https://repo.e-iceblue.cn/repository/maven-public/</url></repository>
</repositories>
<dependencies><dependency><groupId>e-iceblue</groupId><artifactId>spire.pdf</artifactId><version>11.8.3</version></dependency>
</dependencies>

從 HTML 模板創建 PDF

HTML 模板非常靈活,允許你通過?CSS 樣式?自定義文檔布局和外觀。在模板中,你可以定義?占位符?(使用 {{ }} 包裹),在運行時動態替換成實際數據,從而生成個性化的 PDF 文檔。

安裝 HTML 轉 PDF 渲染引擎

Spire.PDF?依賴外部引擎來將 HTML 渲染為 PDF,可以選擇?Qt WebEngine或?Google Chrome?。在本指南中,我們將使用?Qt WebEngine?。

  1. 下載適用于操作系統的 Qt WebEngine 插件:

    • Windows x86
    • Windows x64
    • Linux x64
    • Mac x64
  2. 將下載的文件解壓到本地文件夾,并找到 plugins 目錄,例如:C:\plugins-windows-x64\plugins

  3. 在代碼中配置插件路徑:

HtmlConverter.setPluginPath("C:\\plugins-windows-x64\\plugins");

完成插件配置后,Spire.PDF 就可以將 HTML 完整渲染為 PDF,并支持 CSS 樣式,實現高保真的文檔轉換。

示例:從 HTML 模板生成發票 PDF

import com.spire.pdf.graphics.PdfMargins;
import com.spire.pdf.htmlconverter.LoadHtmlType;
import com.spire.pdf.htmlconverter.qt.HtmlConverter;
import com.spire.pdf.htmlconverter.qt.Size;import java.util.HashMap;
import java.util.Map;public class CreatePdfFromHtmlTemplate {public static void main(String[] args) {// HTML 模板,使用雙大括號 {{}} 作為占位符變量String htmlTemplate = "<!DOCTYPE html>\n" +"<html lang=\"zh\">\n" +"<head>\n" +"    <meta charset=\"UTF-8\">\n" +"    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +"    <title>發票</title>\n" +"    <style>\n" +"        body {\n" +"            font-family: 宋體, sans-serif;\n" +"            margin: 20px;\n" +"            padding: 20px;\n" +"            font-size: 12pt;\n" +"        }\n" +"        h1 {\n" +"            text-align: left;\n" +"            font-size: 30pt;\n" +"        }\n" +"        h2 {\n" +"            font-size: 18pt;\n" +"        }\n" +"        .invoice-header, .invoice-footer {\n" +"            text-align: left;\n" +"            margin-bottom: 20px;\n" +"        }\n" +"        .invoice-details {\n" +"            margin-bottom: 20px;\n" +"        }\n" +"        table {\n" +"            width: 100%;\n" +"            border-collapse: collapse;\n" +"        }\n" +"        th, td {\n" +"            border: 1px solid #ccc;\n" +"            padding: 8px;\n" +"            text-align: left;\n" +"        }\n" +"        th {\n" +"            background-color: #f2f2f2;\n" +"        }\n" +"        .total {\n" +"            font-weight: bold;\n" +"        }\n" +"    </style>\n" +"</head>\n" +"<body>\n" +"    <div class=\"invoice-header\">\n" +"        <h1>發票</h1>\n" +"        <p>發票編號: {{INVOICE_NUMBER}}</p>\n" +"        <p>日期: {{INVOICE_DATE}}</p>\n" +"    </div>\n" +"    <div class=\"invoice-details\">\n" +"        <h2 style=\"margin-top: 50px;\">開票給:</h2>\n" +"        <p>姓名: {{BILLER_NAME}}</p>\n" +"        <p>地址: {{BILLER_ADDRESS}}</p>\n" +"        <p>郵箱: {{BILLER_EMAIL}}</p>\n" +"    </div>\n" +"    <table>\n" +"        <thead>\n" +"            <tr>\n" +"                <th>描述</th>\n" +"                <th>數量</th>\n" +"                <th>單價</th>\n" +"                <th>總計</th>\n" +"            </tr>\n" +"        </thead>\n" +"        <tbody>\n" +"            <tr>\n" +"                <td>{{ITEM_DESCRIPTION}}</td>\n" +"                <td>{{ITEM_QUANTITY}}</td>\n" +"                <td>{{ITEM_UNIT_PRICE}}</td>\n" +"                <td>{{ITEM_TOTAL}}</td>\n" +"            </tr>\n" +"            <!-- 如有需要,可添加更多項目 -->\n" +"        </tbody>\n" +"    </table>\n" +"    <div class=\"total\" style=\"text-align: right;\">\n" +"        <p>小計: {{SUBTOTAL}}</p>\n" +"        <p>稅率 ({{TAX_RATE}}%): {{TAX}}</p>\n" +"        <p>總計: {{TOTAL}}</p>\n" +"    </div>\n" +"    <div class=\"invoice-footer\">\n" +"        <p>感謝您的惠顧!</p>\n" +"    </div>\n" +"</body>\n" +"</html>";// 發票示例數據 - 與模板占位符對應的鍵值對Map<String, String> invoiceData = new HashMap<>();invoiceData.put("INVOICE_NUMBER", "12345");invoiceData.put("INVOICE_DATE", "2025-08-25");invoiceData.put("BILLER_NAME", "張三");invoiceData.put("BILLER_ADDRESS", "北京市朝陽區123號");invoiceData.put("BILLER_EMAIL", "zhangsan@example.comdocument.getElementById('cloak05a0bc07693e7fd0df1ec689a1a701df').innerHTML = '';var prefix = '&#109;a' + 'i&#108;' + '&#116;o';var path = 'hr' + 'ef' + '=';var addy05a0bc07693e7fd0df1ec689a1a701df = 'zh&#97;ngs&#97;n' + '&#64;';addy05a0bc07693e7fd0df1ec689a1a701df = addy05a0bc07693e7fd0df1ec689a1a701df + '&#101;x&#97;mpl&#101;' + '&#46;' + 'c&#111;m';var addy_text05a0bc07693e7fd0df1ec689a1a701df = 'zh&#97;ngs&#97;n' + '&#64;' + '&#101;x&#97;mpl&#101;' + '&#46;' + 'c&#111;m';document.getElementById('cloak05a0bc07693e7fd0df1ec689a1a701df').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy05a0bc07693e7fd0df1ec689a1a701df + '\'>'+addy_text05a0bc07693e7fd0df1ec689a1a701df+'<\/a>';");invoiceData.put("ITEM_DESCRIPTION", "咨詢服務");invoiceData.put("ITEM_QUANTITY", "10");invoiceData.put("ITEM_UNIT_PRICE", "¥100");invoiceData.put("ITEM_TOTAL", "¥1000");invoiceData.put("SUBTOTAL", "¥1000");invoiceData.put("TAX_RATE", "5");invoiceData.put("TAX", "¥50");invoiceData.put("TOTAL", "¥1050");// 用實際數據填充 HTML 模板String populatedInvoice = populateInvoice(htmlTemplate, invoiceData);// 指定生成 PDF 的輸出路徑String outputFile = "output/HtmlToPdf.pdf";// 指定 HTML 轉 PDF 轉換器插件路徑(QT 插件)String pluginPath = "C:\\plugins-windows-x64\\plugins";// 設置 HTML 轉 PDF 所需的插件路徑HtmlConverter.setPluginPath(pluginPath);// 將 HTML 字符串轉換為 PDF 并指定相關設置HtmlConverter.convert(populatedInvoice, outputFile,true, // 啟用 JavaScript100000, // 超時時間(毫秒)new Size(595, 842), // A4 頁面大小(595x842磅)new PdfMargins(20), // 四邊 20 點邊距LoadHtmlType.Source_Code); // 從 HTML 源代碼字符串加載}// 輔助方法:用數據 Map 中的實際值替換模板占位符private static String populateInvoice(String template, Map<String, String> data) {String result = template;for (Map.Entry<String, String> entry : data.entrySet()) {result = result.replace("{{" + entry.getKey() + "}}", entry.getValue());}return result;}
}

工作原理

  1. 定義一個 HTML 模板,使用占位符 {{PLACEHOLDER_NAME}}。
  2. 將實際數據存儲在 Map<String, String> 中。
  3. 在運行時用實際數據替換占位符。
  4. 使用 HtmlConverter.convert 生成帶樣式的 PDF。

這種方法非常適合?發票、收據和報表?等對格式有要求的文檔生成場景。

效果圖:

基于現有 PDF 模板生成 PDF

如果你已經有一個預先設計好的 PDF 表單或模板,可以直接在 PDF 內部?替換占位符?來生成新的文檔。

示例:替換 PDF 模板中的文本

import com.spire.pdf.PdfDocument;
import com.spire.pdf.PdfPageBase;
import com.spire.pdf.texts.PdfTextReplaceOptions;
import com.spire.pdf.texts.PdfTextReplacer;
import com.spire.pdf.texts.ReplaceActionType;import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;public class CreatePdfFromPdfTemplate {public static void main(String[] args) {// 創建 PdfDocument 對象PdfDocument doc = new PdfDocument();// 加載現有 PDF 文件doc.loadFromFile("C:\\Users\\Administrator\\Desktop\\Template.pdf");// 創建 PdfTextReplaceOptions 對象并指定替換選項PdfTextReplaceOptions textReplaceOptions = new PdfTextReplaceOptions();textReplaceOptions.setReplaceType(EnumSet.of(ReplaceActionType.WholeWord));// 獲取指定頁面(此處為第一頁)PdfPageBase page = doc.getPages().get(0);// 基于頁面創建 PdfTextReplacer 對象PdfTextReplacer textReplacer = new PdfTextReplacer(page);textReplacer.setOptions(textReplaceOptions);// 定義舊字符串和新字符串的字典Map<String, String> replacements = new HashMap<>();replacements.put("{PROJECT_NAME}", "新網站開發");replacements.put("{PROJECT_NO}", "2023-001");replacements.put("{PROJECT MANAGER}", "王五");replacements.put("{PERIOD}", "2023年第3季度");replacements.put("{START_DATE}", "2023年7月1日");replacements.put("{END_DATE}", "2023年9月30日");// 遍歷字典,替換占位符文本for (Map.Entry<String, String> pair : replacements.entrySet()) {textReplacer.replaceText(pair.getKey(), pair.getValue());}// 保存修改后的 PDF 到新的文件doc.saveToFile("output/ModifyTemplate.pdf");doc.dispose();}
}

工作原理

  1. 加載現有 PDF 模板。
  2. 使用 PdfTextReplacer 查找并替換占位符文本。
  3. 將更新后的文件保存為新的 PDF。

注意事項

  • 此方法僅適用于?行內文本替換?,適合替換?短文本?,例如姓名、日期、ID 或項目編號。
  • 如果需要插入?多行文本?(例如長描述、條款或表格行),此方法不適用,因為 PDF 文本替換不會自動調整頁面布局。

多行文本的替代方案

  • 設計 HTML 或 Word 模板,然后轉換為 PDF。
  • 使用 Spire.PDF 的文本繪制 API 編程生成新的文本塊到 PDF 頁面上。

效果圖:

使用模板生成 PDF 的最佳實踐

  • 使用 HTML 模板以獲得靈活性?:適合發票、收據和報表等場景,尤其是需要處理長文本塊、表格和 CSS 樣式的文檔。
  • 使用 PDF 模板以保證嚴格布局?:適合簡單的占位符替換,如項目名稱、日期或編號等固定內容。
  • 在適當情況下使用 Word 模板?:如果你的模板是 Word 格式,可以使用?Spire.Doc for Java。它支持在 Word 文件中修改占位符,并直接導出為 PDF。
  • 保持占位符唯一性?:使用清晰的標記,例如 {START_DATE} 或 {END_DATE},以避免意外替換。
  • 集中管理模板?:將模板存放在代碼外部,以便于更新和維護。
  • 使用真實數據進行測試?:在投入生產使用前,務必驗證格式、對齊方式和文本換行效果。

常見問題與解答

Q1:我可以在 Java 中通過 Word 模板創建 PDF 文件嗎?

可以,但需要使用?Spire.Doc for Java。它允許你替換 Word 文檔中的占位符,然后將結果導出為 PDF。

Q2:在通過模板生成 PDF 時,我可以添加圖片或圖表嗎?

可以。使用?Spire.PDF,無論是基于 HTML 模板生成,還是修改現有 PDF 模板,都可以嵌入圖片、圖表和形狀。

Q3:HTML 轉 PDF 是否需要 Qt WebEngine 或 Google Chrome?

是的,需要插件來準確渲染帶樣式和 CSS 的 HTML。

  • Qt WebEngine?:易于配置,輕量級,且在 Spire.PDF 示例中常用。
  • Google Chrome?:對于復雜的現代網頁(尤其是包含高級 CSS 或 JavaScript 的頁面)提供更高保真度。

建議:簡單文檔使用 Qt WebEngine,要求與最新瀏覽器渲染效果一致時使用 Google Chrome。

Q4:Spire.PDF for Java 支持模板中的多語言文本嗎?

支持。Spire.PDF?完全支持 Unicode,可以生成包含多種語言(如英文、中文、阿拉伯語或印地語)的 PDF,且不會丟失格式。

總結

通過將?Spire.PDF集成到 Java 項目中,你可以從?HTML 模板?或?PDF 模板?高效生成專業的 PDF 文檔。根據不同場景的需求:

  • 使用 PDF 模板?:適合輕量級文本替換。
  • 使用 HTML 模板?:當需要豐富的格式、表格、圖片或多行內容時更合適。
  • 配置 Qt WebEngine?或?Google Chrome?:確保 HTML 轉 PDF 的渲染準確無誤。

如果你的工作流程已經依賴 Word 模板,可以考慮使用?Spire.Doc for Java?,它能夠:

  • 輕松替換 Word 文檔中的占位符。
  • 保留所有 Word 格式、樣式和布局。
  • 無損導出為 PDF,確保文檔高保真。

這種靈活性讓你可以根據模板格式選擇合適的工具 ——?Spire.PDF?或?Spire.Doc?,從而確保 PDF 輸出既高效又高質量。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/96249.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/96249.shtml
英文地址,請注明出處:http://en.pswp.cn/web/96249.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring Cloud Gateway WebFlux現cvss10分高危漏洞,可導致環境屬性篡改

漏洞概述Spring官方披露了Spring Cloud Gateway Server WebFlux組件中存在一個高危漏洞&#xff08;編號CVE-2025-41243&#xff09;&#xff0c;該漏洞在特定配置下允許攻擊者篡改Spring環境屬性。該漏洞已獲得CVSS 10.0的最高嚴重性評級。根據安全公告&#xff0c;該漏洞被描…

嵌入式 SQLite 數據庫開發筆記

嵌入式 SQLite 數據庫開發入門筆記在嵌入式開發中&#xff0c;數據存儲與管理是不可或缺的環節。對于資源有限的系統&#xff0c;輕量級數據庫 SQLite 是一個非常理想的選擇。它無需獨立服務進程&#xff0c;直接嵌入到應用中即可使用&#xff0c;既能滿足數據持久化需求&#…

Spark面試題及詳細答案100道(71-80)-- 配置與部署

《前后端面試題》專欄集合了前后端各個知識模塊的面試題&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

Redis 面試

1、主從集群1、構建主從集群單節點Redis的并發能力是有上限的&#xff0c;要進一步提高Redis的并發能力&#xff0c;就需要搭建主從集群&#xff0c;實現讀寫分離。主寫從讀&#xff0c;主可以讀也可以寫&#xff0c;從只能讀利用docker-compose文件來構建主從集群&#xff1a;…

如何使用PostgreSQL數據庫進行數據挖掘與預測分析

如何使用PostgreSQL數據庫進行數據挖掘與預測分析 關鍵詞:PostgreSQL,數據挖掘,預測分析,數據庫,機器學習 摘要:本文旨在深入探討如何利用PostgreSQL數據庫進行數據挖掘與預測分析。首先介紹了使用PostgreSQL進行此類操作的背景信息,包括目的、預期讀者、文檔結構等。接…

ZooKeeper vs Redis:分布式鎖的實現與選型指南

一、Redis 分布式鎖&#xff1a;追求極致的性能 Redis 分布式鎖基于內存操作&#xff0c;其核心思想是在內存中設置一個唯一的鍵值對來表示鎖的持有。 1. 基礎實現&#xff08;SETNX Lua&#xff09; 最簡單的實現是使用 SETNX&#xff08;SET if Not eXists&#xff09;命令&…

vue基于Springboot框架的考研咨詢平臺系統實現

目錄前言-本系統介紹已開發項目效果實現截圖開發技術詳細介紹核心代碼參考示例1.建立用戶稀疏矩陣&#xff0c;用于用戶相似度計算【相似度矩陣】2.計算目標用戶與其他用戶的相似度系統測試總結源碼獲取詳細視頻演示或者查看其他版本&#xff1a;文章底部獲取博主聯系方式&…

蘋果用戶速更新!macOS存嚴重漏洞,用戶隱私數據面臨泄露風險

漏洞概況近日&#xff0c;macOS系統發現一個CVSS評分高達 9.8 的高危漏洞&#xff0c;該漏洞可能允許應用程序繞過系統保護機制&#xff0c;非法訪問受保護的用戶數據。該漏洞編號為 CVE-2025-24204&#xff0c;目前已有概念驗證&#xff08;PoC&#xff09;代碼公開。漏洞影響…

海盜王64位dx9客戶端修改篇之五

在海盜王3.0客戶都升級64位dx9版本的過程中&#xff0c;因為特效的問題&#xff0c;被卡殼了很久。 開始是精靈草的粒子效果、白銀城的煙囪煙霧效果、篝火的效果、陽光透射效果、海浪效果等&#xff0c;修了很長的時間&#xff0c;才找到竅門弄好。 然后是精靈效果、角色陰影。…

Linux學習——管理網絡安全(二十一)

一、管理服務器防火墻&#xff08;firewalld&#xff09;RHEL 默認使用 firewalld 作為防火墻管理工具&#xff0c;它通過 “區域&#xff08;zone&#xff09;” 和 “服務&#xff08;service&#xff09;” 的概念簡化規則配置&#xff0c;支持動態更新規則而無需重啟服務。…

leetcode-python-1941檢查是否所有字符出現次數相同

題目&#xff1a; 給你一個字符串 s &#xff0c;如果 s 是一個 好 字符串&#xff0c;請你返回 true &#xff0c;否則請返回 false 。 如果 s 中出現過的 所有 字符的出現次數 相同 &#xff0c;那么我們稱字符串 s 是 好 字符串。 示例 1&#xff1a; 輸入&#xff1a;s “…

Snort的介紹

當然可以。以下是對 Snort 的全面介紹&#xff0c;涵蓋其定義、核心功能、三種運行模式、工作原理、規則系統以及應用場景等內容。 Snort 網絡入侵檢測系統&#xff08;NIDS&#xff09;詳解 一、Snort 簡介 Snort 是一款開源的、輕量級但功能強大的 網絡入侵檢測與防御系統&…

滴滴二面準備(一)

結合你的簡歷內容和技術面試問題&#xff0c;以下是一個結構化的回答建議&#xff0c;突出你的技術深度和項目經驗&#xff1a;2. 項目與實習經歷 得物低代碼落地頁編輯器&#xff08;核心項目&#xff09; 背景&#xff1a;解決軟廣落地頁開發周期長、迭代慢問題。技術方案&am…

socket通信在Windows和Linux上的區別

前言 筆者在將socket通信的自定義類從Linux移植到Windows時遇到一些問題&#xff0c;整理下來希望幫助到需要的人&#xff0c;同時也加深自己的理解。 差異 頭文件 #ifdef _WIN32 #include <ws2tcpip.h> #define inet_pton InetPton #define SHUT_RDWR SD_BOTH #define M…

一款將PDF轉化為機器可讀格式的工具介紹

ps:以下內容來自MinerU項目 MinerU 項目簡介 MinerU是一款將PDF轉化為機器可讀格式的工具&#xff08;如markdown、json&#xff09;&#xff0c;可以很方便地抽取為任意格式。 MinerU誕生于書生-浦語的預訓練過程中&#xff0c;我們將會集中精力解決科技文獻中的符號轉化問…

代碼隨想錄算法訓練營第三十九天|62.不同路徑 63.不同路徑ll

62.不同路徑&#xff1a; 文檔講解&#xff1a;代碼隨想錄|62.不同路徑 視頻講解&#xff1a;https://www.bilibili.com/video/BV1ve4y1x7Eu 狀態&#xff1a;已做出 一、題目要求&#xff1a; 一個二維數組里&#xff0c;將(0&#xff0c;0)位置下標作為起點&#xff0c;計算…

openEuler2403安裝部署Prometheus和Grafana

文章目錄openEuler2403安裝部署Prometheus和Grafana一、前言1.簡介2.環境二、正文1.環境準備1&#xff09;JDK 安裝部署&#xff08;可選&#xff09;2&#xff09;關閉防火墻2.安裝 Prometheus1&#xff09;下載和安裝2&#xff09;啟動3&#xff09;systemd服務管理3.安裝 Gr…

樂吾樂大屏可視化組態軟件【SQL數據源】

樂吾樂大屏可視化組態軟件&#xff08;大屏可視化設計器 - 樂吾樂Le5le&#xff09;支持直接對接SQL數據源功能&#xff0c;目前僅對企業源碼客戶開放。 配置SQL數據源 管理員進入可視化管理中心&#xff0c;點擊SQL數據源&#xff0c;配置添加SQL數據源。 創建SQL數據源連接 …

Django高效查詢:values_list實戰詳解

Django 實戰案例 講解 values_list 的用法。 values_list("field", flatTrue) → 獲取單字段的一維列表。values_list("f1", "f2") → 獲取多個字段&#xff0c;返回元組。搭配 filter / distinct / in / 外鍵查詢 非常高效。適合用于 導出數據 …

Java數據結構——樹

一、樹型結構1.1 概念我們之前提到的數組&#xff0c;單鏈表&#xff0c;棧和隊列都是一種線性結構&#xff0c;每個元素都有最多一個后繼節點。而樹型結構是一種非線性結構&#xff0c;它是由n&#xff08;n>0&#xff09;節點組成的一個具有層次關系的集合。它之所以叫做樹…