日常工作中,动辄几兆、几十兆的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_QUALITY | float | 0.3f | 图片压缩质量(取值0.0-1.0),值越小压缩率越高,画质相对越低 |
| DEFAULT_DPI | int | 72 | 渲染PDF页面的DPI分辨率,值越小文件体积越小,分辨率越低 |
| MIN_COMPRESSION_RATIO | double | 0.05 | 最小压缩收益阈值(5%),若压缩率低于该值,视为无效压缩,返回原始文件 |
| pdfData | byte[] | - | 待压缩的原始PDF字节数组,不能为空或空数组 |
| quality | float | DEFAULT_QUALITY | 自定义压缩质量,需在0.0-1.0范围内 |
| dpi | int | DEFAULT_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
}
}
五、工具核心特性
- 智能兜底:压缩率低于5%时自动返回原始文件,避免无效操作;
- 自动适配:压缩后页面自动适配A4尺寸,居中渲染,保证排版;
- 异常处理:完善的异常捕获与日志输出,便于排查压缩失败原因;
- 资源安全:通过finally块确保PDF文档、流资源正常关闭,避免内存泄漏。
六、使用小贴士
- 压缩质量建议在0.2-0.5之间,兼顾体积和画质;
- 办公场景推荐DPI72,纯文本PDF可降至50,高清图文建议96;
- 该工具基于「将PDF页面转为图片再重绘」逻辑,适合以图片为主的PDF,纯文本PDF压缩收益可能较低。
微信公众号: