1. 概述
本文将介绍如何使用 Java 8 内置工具和 Apache Commons Codec 实现 PDF 文件的 Base64 编码与解码。
虽然 Base64 是个老生常谈的话题,但在处理文件上传、API 接口传输二进制数据时依然高频出现。尤其是前端通过 JSON 传输文件内容时,Base64 是最常见的方案之一。我们结合 PDF 这种典型二进制文件来实战演示。
顺带提一句:别把 Base64 当加密手段,它只是编码(encoding),不是加密(encryption)✅。
2. Base64 基础原理
在网络传输中,原始二进制数据(比如图片、PDF)直接发送容易出问题。不同协议对字节流的解释可能不一致,导致数据“在路上”就被篡改或损坏 ❌。
Base64 的出现就是为了解决这个问题 —— 把任意二进制数据编码成只包含 A-Z、a-z、0-9、+、/(以及 = 作填充)的安全 ASCII 字符集,确保跨平台、跨协议传输时不会出错。
✅ 举个类比:就像寄送易碎品要打包进泡沫箱一样,Base64 就是给二进制数据加了个“保护壳”。
编码后数据体积会膨胀约 33%,这是代价,但换来的是极高的兼容性。对于 PDF 这类文档,在小文件场景下完全可接受。
3. 使用 Java 8 原生 API 转换
从 Java 8 开始,JDK 提供了 java.util.Base64
工具类,无需引入第三方依赖,简单粗暴直接用 ✅。
该类支持三种模式:
- Basic:标准 Base64(本文使用)
- URL and Filename Safe:替换
/
和+
为-
和_
,适合 URL 场景 - MIME:支持每 76 字符换行,符合邮件标准
3.1 编码:PDF → Base64
先读取 PDF 文件为字节数组,再通过 Base64.getEncoder().encode()
完成编码:
byte[] inFileBytes = Files.readAllBytes(Paths.get("input.pdf"));
byte[] encoded = java.util.Base64.getEncoder().encode(inFileBytes);
其中:
"input.pdf"
是输入 PDF 文件路径- 编码结果是
byte[]
,若需字符串可用new String(encoded)
转换
⚠️ 注意:大文件不要一次性加载进内存,否则容易 OOM。
3.2 流式编码(推荐用于大文件)
对于较大 PDF 文件(如超过 10MB),建议使用流式处理,避免内存溢出:
try (OutputStream os = java.util.Base64.getEncoder().wrap(new FileOutputStream("output.base64"));
FileInputStream fis = new FileInputStream("input.pdf")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
}
关键点:
Base64.getEncoder().wrap(outputStream)
返回一个包装过的输出流- 每次读取最多 1KB 数据,边读边编码写入目标文件
- 整个过程内存占用恒定,适合生产环境处理大文件 ✅
3.3 解码:Base64 → PDF
收到 Base64 数据后,调用 getDecoder().decode()
还原为原始字节,并写入新文件:
byte[] decoded = java.util.Base64.getDecoder().decode(encoded);
try (FileOutputStream fos = new FileOutputStream("decoded.pdf")) {
fos.write(decoded);
fos.flush();
}
✅ 最佳实践:使用 try-with-resources 自动关闭流,防止资源泄漏。
4. 使用 Apache Commons Codec 转换
如果你还在维护 JDK 7 或更老版本项目,或者需要兼容多个 JVM 厂商环境,Apache Commons Codec 是个稳定选择。
4.1 Maven 依赖
添加以下依赖即可:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.16.0</version>
</dependency>
📌 最新版本可查看 Maven Central
4.2 编码:PDF → Base64
用法与 Java 8 类似,调用 Base64.encodeBase64()
方法:
byte[] inFileBytes = Files.readAllBytes(Paths.get("input.pdf"));
byte[] encoded = org.apache.commons.codec.binary.Base64.encodeBase64(inFileBytes);
注意类路径是 org.apache.commons.codec.binary.Base64
。
4.3 流式编码支持情况
❌ 遗憾的是,Commons Codec 不支持流式编码,必须一次性将整个文件加载到内存中。这意味着处理大文件时存在内存风险。
4.4 解码:Base64 → PDF
解码同样简单:
byte[] decoded = org.apache.commons.codec.binary.Base64.decodeBase64(encoded);
try (FileOutputStream fos = new FileOutputStream("decoded.pdf")) {
fos.write(decoded);
fos.flush();
}
5. 测试验证
下面是一个完整的 JUnit 测试类,验证编码解码的正确性:
public class EncodeDecodeUnitTest {
private static final String IN_FILE = "src/test/resources/sample.pdf";
private static final String OUT_FILE = "target/decoded.pdf";
private static byte[] inFileBytes;
@BeforeClass
public static void fileToByteArray() throws IOException {
inFileBytes = Files.readAllBytes(Paths.get(IN_FILE));
}
@Test
public void givenJavaBase64_whenEncoded_thenDecodedOK() throws IOException {
byte[] encoded = java.util.Base64.getEncoder().encode(inFileBytes);
byte[] decoded = java.util.Base64.getDecoder().decode(encoded);
writeToFile(OUT_FILE, decoded);
assertNotEquals(encoded.length, decoded.length);
assertEquals(inFileBytes.length, decoded.length);
assertArrayEquals(decoded, inFileBytes);
}
@Test
public void givenJavaBase64_whenEncodedStream_thenDecodedStreamOK() throws IOException {
try (OutputStream os = java.util.Base64.getEncoder().wrap(new FileOutputStream("target/streamed.base64"));
FileInputStream fis = new FileInputStream(IN_FILE)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) > -1) {
os.write(buffer, 0, bytesRead);
}
}
byte[] encoded = java.util.Base64.getEncoder().encode(inFileBytes);
byte[] encodedOnDisk = Files.readAllBytes(Paths.get("target/streamed.base64"));
assertArrayEquals(encoded, encodedOnDisk);
byte[] decoded = java.util.Base64.getDecoder().decode(encoded);
byte[] decodedOnDisk = java.util.Base64.getDecoder().decode(encodedOnDisk);
assertArrayEquals(decoded, decodedOnDisk);
}
@Test
public void givenApacheCommons_givenJavaBase64_whenEncoded_thenDecodedOK() throws IOException {
byte[] encoded = org.apache.commons.codec.binary.Base64.encodeBase64(inFileBytes);
byte[] decoded = org.apache.commons.codec.binary.Base64.decodeBase64(encoded);
writeToFile(OUT_FILE, decoded);
assertNotEquals(encoded.length, decoded.length);
assertEquals(inFileBytes.length, decoded.length);
assertArrayEquals(decoded, inFileBytes);
}
private void writeToFile(String fileName, byte[] bytes) throws IOException {
try (FileOutputStream fos = new FileOutputStream(fileName)) {
fos.write(bytes);
}
}
}
测试要点:
- ✅ 编码前后长度不同(膨胀)
- ✅ 解码后长度和内容与原始文件一致
- ✅ 支持跨实现互操作(Java 8 与 Commons 编码结果兼容)
💡 小技巧:最终生成的
decoded.pdf
可手动打开比对内容,确保视觉上无差异。
6. 总结
本文演示了两种主流方式实现 PDF 与 Base64 的相互转换:
方式 | 优点 | 缺点 |
---|---|---|
✅ Java 8 Base64 |
原生支持、性能高、支持流式处理 | 需 JDK 8+ |
⚠️ Apache Commons Codec | 兼容老版本 JDK | 不支持流式、性能略低 |
📌 推荐策略:
- 新项目统一使用
java.util.Base64
✅ - 老系统可继续用 Commons Codec,但注意大文件内存风险 ❌
🔗 示例代码已托管至 GitHub:https://github.com/your-repo/pdf-base64-demo
Base64 看似简单,但在实际项目中经常踩坑,比如忘记处理换行符、误用加密场景等。掌握其本质和正确用法,能少走很多弯路。