分类
Java

Java:Base64编码与解码

Java Base64 Encoding and Decoding

译者前言

原文地址:https://www.baeldung.com/java-base64-encode-and-decode
Demo仓库地址:https://github.com/liuyuxuan6666/java-base64-encode-demo

1.概述

在这个教程中,我们将会学习Java提供的Base64编码和解码功能。
我们主要讨论的是Java8中的API,顺便也会说一说Apache Commons包中的部分API。

2.Java8中Base64的使用

Java8通过java.util.Base64这个类,在标准API中实现了Base64编解码的相关功能。我们先从最基本的用法开始。

2.1. Java8中Base64的基本编码器(getEncoder)

“基本编码器”是最简单的编码方式,直接处理输入的字符,不进行任何分隔处理。

也就是把输入的字符按照某种确定的规则,映射到由"A-Z a-z 0-9 +/"这些字符组成字符集中,形成一个字符序列。

我们先来看简单的String字符串处理(代码文件见 demo_2_1):

// 设置需要编码的字符串
String originalInput = "test input";
// 直接使用Base64.getEncoder()方法编码
String encodedString = Base64.getEncoder().encodeToString(originalInput.getBytes());
// 将会输出 dGVzdCBpbnB1dA==

需要注意的是,此处我们直接使用基本的getEncoder()方法,来调用内部API,实现Base64的编码。

对应的解码,把getEncoder()替换为getDecoder()即可,这里注意,转换过程是先转换为字节流,再转换为字符串:

String encodedString = "dGVzdCBpbnB1dA==";
// 使用Base64.getDecoder()获得字节流  byte[10]: [116, 101, 115, 116, 32, 105, 110, 112, 117, 116]
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
// 新建一个String变量,把字节流转化成字符串
String decodedString = new String(decodedBytes);
// 将会输出 test input

译者注:如果想查看转换中的字节流是什么形式,打断点或加输出语句可以更好的帮助学习,注释中提供了字节流。

2.2. 无填充的Java8 Base64编码(withoutPadding)

在Base64编码中,输出的字符串长度必须是3的倍数。如果编码后长度不是3的倍数,编码器会在字符串的末尾添加1或2个字符来满足需要,这个字符是等号“=”(参考2.1,编码后的字符串中有两个等号)。
解码时,编码器先把这些等号去掉,再开始解码。
对于Base64填充的深入理解,可以参考Stack Overflow上的详细解释

但有的时候,我们不需要编码器自动为我们填充,例如某些情况下,只需要Base64编码而不需要解码,这个时候就可以不使用填充,而是直接进行编码。
使用方式也很简单,只需要在执行编码之前添加.withoutPadding()即可(demo_2_2):

String encodedString = Base64.getEncoder().withoutPadding().encodeToString(originalInput.getBytes());
// 将会输出 dGVzdCBpbnB1dA

与2.1对比,同样的字符串采用填充和无填充编码的唯一区别,就是去掉了末尾的等号。

2.3. Java8 URL编码(getUrlEncoder)

URL编码器和基本编码器非常相似,不同之处在于:

  1. 它使用URL和文件名安全的Base64字母表,也就是编码后的字符串符合文件命名规范
  2. 它默认不添加任何行分隔符

使用方法,只需要把基本编码器中的getEncoder替换为getUrlEncoder,如下(demo_2_3):

String originalUrl = "https://www.google.co.nz/?gfe_rd=cr&ei=dzbFV&gws_rd=ssl#q=java";
String encodedUrl = Base64.getUrlEncoder().encodeToString(originalUrl.getBytes());
// 将会输出 aHR0cHM6Ly93d3cuZ29vZ2xlLmNvLm56Lz9nZmVfcmQ9Y3ImZWk9ZHpiRlYmZ3dzX3JkPXNzbCNxPWphdmE=

解码也是相同的用法,这里使用getUrlDecoder,和前面普通字符串的解码相似,先转换为字节流,再转换为字符串:

byte[] decodedBytes = Base64.getUrlDecoder().decode(encodedUrl);
// byte[62]:
// [104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46,
// 103, 111, 111, 103, 108, 101, 46, 99, 111, 46, 110, 122,
// 47, 63, 103, 102, 101, 95, 114, 100, 61, 99, 114, 38, 101,
// 105, 61, 100, 122, 98, 70, 86, 38, 103, 119, 115, 95, 114,
String decodedUrl = new String(decodedBytes);
// 将会输出 https://www.google.co.nz/?gfe_rd=cr&ei=dzbFV&gws_rd=ssl#q=java

2.4. Java8 MIME编码(getMimeEncoder)

什么是MIME?菜鸟教程中给出了详细解答:

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的标准,用来表示文档、文件或字节流的性质和格式。
MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。
浏览器通常使用 MIME 类型(而不是文件扩展名)来确定如何处理URL,因此 Web服务器在响应头中添加正确的 MIME 类型非常重要。如果配置不正确,浏览器可能会无法解析文件内容,网站将无法正常工作,并且下载的文件也会被错误处理。

我们先从基本的MIME输入开始编码(demo_2_4):

private static StringBuilder getMimeBuffer() {
    StringBuilder buffer = new StringBuilder();
    for (int count = 0; count < 10; ++count) {
        buffer.append(UUID.randomUUID().toString());
    }
    return buffer;
}

MIME编码器使用基本字母表生成Base64编码的结果,这个编码格式是对于MIME友好的。这体现在:

  1. 每行输出不超过76个字符
  2. 编码后的字符串以回车符和换行符结尾 (\r\n)
StringBuilder buffer = getMimeBuffer();
byte[] encodedAsBytes = buffer.toString().getBytes();
String encodedMime = Base64.getMimeEncoder().encodeToString(encodedAsBytes);

在解码过程中,我们可以使用getMimeDecoder()方法,仍然是字节流->字符串:

byte[] decodedBytes = Base64.getMimeDecoder().decode(encodedMime);
String decodedMime = new String(decodedBytes);

3. 使用Apache Commons Code包进行编码解码

前面提到的是用Java8内置的Base64 API来操作,此外,还有其他的第三方包也提供了Base64的一些功能,例如Apache Commons
假设我们使用的是Maven托管的项目,再pom.xml中添加commons-codec的依赖:

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.15</version>
</dependency>

译者注:如果你第一次接触maven,添加依赖后需要在项目根目录执行一次mvn install,或者在IDEA中选择pom.xml右键-Maven-重新加载项目,即可完成依赖的更新。

我们使用的是名为org.apache.commons.codec.binary.Base64的类,可以通过不同参数的构造方法来初始化一个实例对象:

  1. Base64(boolean urlSafe) —— 创建Base64对象,打开或关闭URL安全模式
  2. Base64(int lineLength) —— 创建Base64对象,不启用URL安全模式,通过参数控制行的长度,默认长度为76
  3. Base64(int lineLength, byte[] lineSeparator) —— 创建Base64对象,参数一控制行的长度,参数二设置额外的分隔符,默认为 CRLF(“\r\n”)

当Base64实例对象生成后,调用它进行编码解码就十分简单了。编码(demo_3):

// 输入需要编码的字符串
String originalInput = "test input";
// 用默认参数创建Base64对象
Base64 base64 = new Base64();
// 执行编码
String encodedString = new String(base64.encode(originalInput.getBytes()));
// 将会输出 dGVzdCBpbnB1dA==

此外,Base64类的decode()方法来进行解码,获得字符串:

String decodedString = new String(base64.decode(encodedString.getBytes()));
// 将会输出 test input

除了上面提到的使用Base64的实例对象的方式,还有一种方式不创建对象,而是直接用Base64类的静态方法:

String originalInput = "test input";
String encodedString = new String(Base64.encodeBase64(originalInput.getBytes()));
// dGVzdCBpbnB1dA==
String decodedString = new String(Base64.decodeBase64(encodedString.getBytes()));
// test input

4. 将字符串对象转化为字节数组

有些情况下,我们需要把String对象转化为byte[],有多种方法。
最简单的是直接使用String对象内置的getBytes()方法(demo_4):

String originalInput = "test input";
byte[] result = originalInput.getBytes();
assertEquals(originalInput.length(), result.length);

我们可以不使用默认的编码方式,而是为其指定一个:

String originalInput = "test input";
byte[] result = originalInput.getBytes(StandardCharsets.UTF_16);
assertTrue(originalInput.length() < result.length);

如果我们输入的是Base64编码后的字符串,想获得原始数据,可以使用Base64解码器:

String originalInput = "dGVzdCBpbnB1dA==";
byte[] result = Base64.getDecoder().decode(originalInput);
assertEquals("test input", new String(result));

我们还可以使用DatatypeConverter类的parseBase64Binary()方法:

String originalInput = "dGVzdCBpbnB1dA==";
byte[] result = DatatypeConverter.parseBase64Binary(originalInput);
assertEquals("test input", new String(result));

5. 总结

在本文中,我们学习了Java中如何使用Base64进行编码和解码。我们分别使用了Java8的原生API和ApacheCommons引入的新API。
除了文中提到的,还有其他相似功能的API,例如java.xml.bind.DataTypeConverter带有带有printHexBinary和parseBase64Binary功能。
要学习其他的API的使用方式,可以从GitHub上寻找更多的代码。