首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 合併 PDF 實戰:基於 iText 5 的多文件合併完整代碼與生產級優化指南

     編輯:Java 相關知識

PDF 合併是報表系統、歸檔服務、電子簽章平台的高頻需求。本文提供基於 iText 5(com.lowagie.text)的完整可用代碼,並針對大文件、內存控制、異常處理、版本兼容性給出生產級優化建議。

一、 核心合併代碼(iText 5)

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfCopy;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import java.io.FileOutputStream;
import java.io.IOException;

public class PdfMergeUtil {
    
    /**
     * 合併多個 PDF 文件
     * @param sourceFiles 源文件絕對路徑數組
     * @param targetFile  合併後輸出文件絕對路徑
     * @return 成功返回 true,失敗返回 false
     */
    public static boolean mergePdfFiles(String[] sourceFiles, String targetFile) {
        if (sourceFiles == null || sourceFiles.length == 0) {
            throw new IllegalArgumentException("源文件列表不能為空");
        }
        
        Document document = null;
        PdfCopy copy = null;
        FileOutputStream fos = null;
        
        try {
            // 1. 以第一個文件的頁面大小創建 Document
            PdfReader firstReader = new PdfReader(sourceFiles[0]);
            document = new Document(firstReader.getPageSize(1));
            firstReader.close(); // 僅獲取頁面大小,及時關閉
            
            // 2. 創建 PdfCopy 寫入輸出流
            fos = new FileOutputStream(targetFile);
            copy = new PdfCopy(document, fos);
            document.open();
            
            // 3. 遍歷所有源文件,逐頁導入
            for (String srcFile : sourceFiles) {
                PdfReader reader = null;
                try {
                    reader = new PdfReader(srcFile);
                    int pageCount = reader.getNumberOfPages();
                    
                    for (int pageNum = 1; pageNum <= pageCount; pageNum++) {
                        document.newPage(); // 必須調用,創建新頁面
                        PdfImportedPage importedPage = copy.getImportedPage(reader, pageNum);
                        copy.addPage(importedPage);
                    }
                } finally {
                    if (reader != null) {
                        reader.close(); // 關鍵:釋放文件句柄,防止句柄洩漏
                    }
                }
            }
            
            return true;
            
        } catch (DocumentException | IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            // 4. 逆序關閉資源
            if (document != null) {
                document.close(); // 會自動關閉 copy
            }
            if (fos != null) {
                try { fos.close(); } catch (IOException ignored) {}
            }
        }
    }
    
    public static void main(String[] args) {
        String[] files = {"e:\\1.pdf", "e:\\2.pdf", "e:\\3.pdf"};
        String output = "e:\\merged.pdf";
        boolean success = mergePdfFiles(files, output);
        System.out.println("合併" + (success ? "成功" : "失敗") + ":" + output);
    }
}

二、 關鍵技術點解析

  1. PdfCopy vs PdfWriter:PdfCopy 專為合併/拆分設計,直接複製頁面內容流,不重新解析渲染,性能極高;PdfWriter 用於創建新內容。
  2. document.newPage() 必須調用:每導入一頁前必須調用,否則頁面內容會疊加在同一頁。
  3. 頁碼從 1 開始:getImportedPage(reader, 1) 為第一頁。
  4. 及時關閉 PdfReader:循環內部關閉,避免大量文件合併時「Too many open files」。
  5. 保持原頁面大小:以首文件頁面大小創建 Document,後續頁面自動縮放適配(可通過 PdfCopy.setMergeFields() 處理表單字段)。

三、 升級指南:iText 7(com.itextpdf)API 差異

iText 5(Lowagie)已停止維護,新項目建議使用 iText 7(AGPL 協議)或 Apache PDFBox(Apache 2.0 協議)。iText 7 核心 API 變更:

// iText 7 風格
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.PdfPage;

public static void mergePdfFilesV7(String[] sources, String target) throws IOException {
    try (PdfWriter writer = new PdfWriter(target);
         PdfDocument pdfDoc = new PdfDocument(writer)) {
        
        for (String src : sources) {
            try (PdfReader reader = new PdfReader(src);
                 PdfDocument srcDoc = new PdfDocument(reader)) {
                
                srcDoc.copyPagesTo(1, srcDoc.getNumberOfPages(), pdfDoc);
            }
        }
    }
}

四、 生產級優化清單

優化點方案
大文件內存壓力 使用 PdfReader.unethicalReading()(iText 5)或流式處理(iText 7/PDFBox),避免全量載入內存
加密 PDF 處理 構造 PdfReader 時傳入密碼:new PdfReader(src, password.getBytes())
表單字段合併 copy.setMergeFields() 保留/重命名字段,避免同名衝突
書籤/目錄保留 copy.setOutlines() 合併書籤樹
異常恢復 臨時文件寫入成功後原子重命名(Files.move(..., REPLACE_EXISTING, ATOMIC_MOVE)),防止輸出損壞
並發合併 線程池並行處理多組合併任務,注意臨時目錄隔離

五、 替代方案:Apache PDFBox(Apache 2.0 許可證)

// PDFBox 3.x 風格
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import java.io.File;

public static void mergeWithPdfBox(String[] sources, String target) throws IOException {
    PDFMergerUtility merger = new PDFMergerUtility();
    for (String src : sources) {
        merger.addSource(new File(src));
    }
    merger.setDestinationFileName(target);
    merger.mergeDocuments(null); // null 表示不加載到內存,流式處理
}

六、 總結

PDF 合併核心在於頁面級流式複製而非重新渲染。iText 5 代碼簡潔高效,但新項目建議遷移至 iText 7 或 PDFBox 獲得長期維護與許可證合規。生產環境務必關注:資源及時釋放、加密文件支持、大文件內存控制、輸出原子性、書籤/表單保留。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved