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 看似简单,但在实际项目中经常踩坑,比如忘记处理换行符、误用加密场景等。掌握其本质和正确用法,能少走很多弯路。


原始标题:Convert PDF to Base64