告别大体积PDF!基于PDFBox的Java压缩工具

0 阅读5分钟

日常工作中,动辄几兆、几十兆的PDF文件不仅传输慢,还占存储空间?今天给大家分享一款基于Apache PDFBox开发的Java PDF压缩工具(PdfCompressUtil工具类),几行代码就能实现PDF文件轻量化,兼顾压缩效率与使用体验,赶紧收藏起来!

一、核心依赖

使用该工具首先要引入PDFBox依赖,Maven配置如下:

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.24</version>
</dependency>

二、工具类全量代码(PdfCompressUtil)

完整的PdfCompressUtil工具类代码如下,可直接复制到项目中(包路径已调整为com.util):

package com.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.PDFRenderer;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;

/**
 * PDF压缩工具类 - 使用Apache PDFBox
 *
 * @author: system
 * @date: 2026/04/07
 */
@Slf4j
public class PdfCompressUtil {

    /**
     * 默认压缩质量 (0.0-1.0)
     */
    private static final float DEFAULT_QUALITY = 0.3f;

    /**
     * 默认DPI分辨率
     */
    private static final int DEFAULT_DPI = 72;

    /**
     * 最小压缩收益阈值(压缩率低于5%视为无效压缩)
     */
    private static final double MIN_COMPRESSION_RATIO = 0.05;

    /**
     * 压缩PDF文件
     *
     * @param pdfData 原始PDF字节数组
     * @return 压缩后的PDF字节数组
     * @throws IOException IO异常
     */
    public static byte[] compressPdf(byte[] pdfData) throws IOException {
        return compressPdf(pdfData, DEFAULT_QUALITY, DEFAULT_DPI);
    }

    /**
     * 压缩PDF文件(指定质量和DPI)
     *
     * @param pdfData 原始PDF字节数组
     * @param quality 压缩质量 (0.0-1.0),值越小压缩率越高
     * @param dpi     DPI分辨率,值越小文件越小
     * @return 压缩后的PDF字节数组
     * @throws IOException IO异常
     */
    public static byte[] compressPdf(byte[] pdfData, float quality, int dpi) throws IOException {
        if (pdfData == null || pdfData.length == 0) {
            throw new IOException("PDF数据为空");
        }

        PDDocument document = null;
        ByteArrayOutputStream outputStream = null;

        try {
            document = PDDocument.load(pdfData);
            
            long originalSize = pdfData.length;
            log.info("开始PDF压缩,原始大小: {} bytes, 页数: {}", originalSize, document.getNumberOfPages());

            outputStream = new ByteArrayOutputStream();
            PDDocument compressedDocument = new PDDocument();

            try {
                PDFRenderer renderer = new PDFRenderer(document);

                for (int i = 0; i < document.getNumberOfPages(); i++) {
                    BufferedImage image = renderer.renderImageWithDPI(i, dpi);

                    PDPage page = new PDPage(PDRectangle.A4);
                    compressedDocument.addPage(page);

                    ByteArrayOutputStream imageOutputStream = new ByteArrayOutputStream();
                    ImageIO.write(image, "jpg", imageOutputStream);
                    byte[] imageBytes = imageOutputStream.toByteArray();

                    PDImageXObject pdImage = PDImageXObject.createFromByteArray(
                            compressedDocument, imageBytes, "page_" + i);

                    try (PDPageContentStream contentStream = new PDPageContentStream(compressedDocument, page)) {
                        float scale = Math.min(
                                page.getMediaBox().getWidth() / pdImage.getWidth(),
                                page.getMediaBox().getHeight() / pdImage.getHeight()
                        );

                        float scaledWidth = pdImage.getWidth() * scale;
                        float scaledHeight = pdImage.getHeight() * scale;

                        float x = (page.getMediaBox().getWidth() - scaledWidth) / 2;
                        float y = (page.getMediaBox().getHeight() - scaledHeight) / 2;

                        contentStream.drawImage(pdImage, x, y, scaledWidth, scaledHeight);
                    }
                }

                compressedDocument.save(outputStream);
                byte[] compressedData = outputStream.toByteArray();
                
                double compressionRatio = 1 - (double) compressedData.length / originalSize;

                if (compressionRatio < MIN_COMPRESSION_RATIO) {
                    log.warn("PDF压缩效果不明显,返回原始文件。原始大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}%",
                            originalSize, compressedData.length, String.format("%.2f", compressionRatio * 100));
                    return pdfData;
                }

                log.info("PDF压缩成功。原始大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}%",
                        originalSize, compressedData.length, String.format("%.2f", compressionRatio * 100));

                return compressedData;

            } finally {
                if (compressedDocument != null) {
                    compressedDocument.close();
                }
            }

        } catch (Exception e) {
            log.error("PDF压缩失败", e);
            throw new IOException("PDF压缩失败: " + e.getMessage(), e);
        } finally {
            if (document != null) {
                document.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }

    /**
     * 判断是否为PDF文件
     *
     * @param fileName 文件名
     * @return true-是PDF文件,false-不是PDF文件
     */
    public static boolean isPdfFile(String fileName) {
        if (fileName == null || fileName.isEmpty()) {
            return false;
        }
        String lowerCaseFileName = fileName.toLowerCase();
        return lowerCaseFileName.endsWith(".pdf");
    }
}

三、核心压缩参数说明

PdfCompressUtil工具类的压缩逻辑围绕「质量」「分辨率」两大核心维度,同时内置了智能阈值判断,以下是关键参数及注释:

参数名类型默认值注释说明
DEFAULT_QUALITYfloat0.3f图片压缩质量(取值0.0-1.0),值越小压缩率越高,画质相对越低
DEFAULT_DPIint72渲染PDF页面的DPI分辨率,值越小文件体积越小,分辨率越低
MIN_COMPRESSION_RATIOdouble0.05最小压缩收益阈值(5%),若压缩率低于该值,视为无效压缩,返回原始文件
pdfDatabyte[]-待压缩的原始PDF字节数组,不能为空或空数组
qualityfloatDEFAULT_QUALITY自定义压缩质量,需在0.0-1.0范围内
dpiintDEFAULT_DPI自定义渲染DPI,可根据需求调整(如50、72、96等)

四、工具使用示例代码

工具类封装了简洁的调用方法,支持「默认参数压缩」和「自定义参数压缩」两种方式,直接复用即可:

1. 基础使用(默认参数)

import com.util.PdfCompressUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class PdfCompressDemo {
    public static void main(String[] args) {
        // 1. 读取待压缩的PDF文件为字节数组
        File inputFile = new File("D:/test/原始文件.pdf");
        byte[] pdfData = new byte[(int) inputFile.length()];
        try (FileInputStream fis = new FileInputStream(inputFile)) {
            fis.read(pdfData);
            
            // 2. 使用默认参数压缩PDF(质量0.3,DPI72)
            byte[] compressedData = PdfCompressUtil.compressPdf(pdfData);
            
            // 3. 将压缩后的字节数组写入新文件
            File outputFile = new File("D:/test/压缩后文件.pdf");
            try (FileOutputStream fos = new FileOutputStream(outputFile)) {
                fos.write(compressedData);
                System.out.println("PDF压缩完成,已保存至:" + outputFile.getAbsolutePath());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. 自定义参数压缩(按需调整质量和DPI)

如果对压缩质量/分辨率有特殊需求,可自定义参数:

import com.util.PdfCompressUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CustomPdfCompressDemo {
    public static void main(String[] args) {
        File inputFile = new File("D:/test/高清PDF.pdf");
        byte[] pdfData = new byte[(int) inputFile.length()];
        try (FileInputStream fis = new FileInputStream(inputFile)) {
            fis.read(pdfData);
            
            // 自定义压缩参数:质量0.2(更高压缩率)、DPI50(更低分辨率)
            float customQuality = 0.2f;
            int customDpi = 50;
            byte[] compressedData = PdfCompressUtil.compressPdf(pdfData, customQuality, customDpi);
            
            File outputFile = new File("D:/test/自定义压缩后文件.pdf");
            try (FileOutputStream fos = new FileOutputStream(outputFile)) {
                fos.write(compressedData);
                System.out.println("自定义参数PDF压缩完成!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. 额外工具方法:判断是否为PDF文件

工具类还内置了文件名校验方法,避免传入非PDF文件导致异常:

// 校验文件名是否为PDF
import com.util.PdfCompressUtil;

public class PdfCheckDemo {
    public static void main(String[] args) {
        String fileName = "报表.pdf";
        boolean isPdf = PdfCompressUtil.isPdfFile(fileName);
        System.out.println("是否为PDF文件:" + isPdf); // 输出true
    }
}

五、工具核心特性

  1. 智能兜底:压缩率低于5%时自动返回原始文件,避免无效操作;
  2. 自动适配:压缩后页面自动适配A4尺寸,居中渲染,保证排版;
  3. 异常处理:完善的异常捕获与日志输出,便于排查压缩失败原因;
  4. 资源安全:通过finally块确保PDF文档、流资源正常关闭,避免内存泄漏。

六、使用小贴士

  • 压缩质量建议在0.2-0.5之间,兼顾体积和画质;
  • 办公场景推荐DPI72,纯文本PDF可降至50,高清图文建议96;
  • 该工具基于「将PDF页面转为图片再重绘」逻辑,适合以图片为主的PDF,纯文本PDF压缩收益可能较低。

微信公众号:

gzh.jpg