引言
Base64编码是一种用64个字符表示二进制数据的方法,它使用一组64个可打印字符来表示二进制数据,每6个比特位为一个单元,对应某个可打印字符。注意它并不是一种加密算法,所以Base64常用于在不支持二进制数据的系统间传输二进制数据。
但是,Java 8 之前并不支持 Base64,我们需要依赖第三方库如Apache Commons Codec
或者在JDK内部类sun.misc.BASE64Encoder
和sun.misc.BASE64Decoder
等不推荐使用的方式来实现Base64编码解码。
为了能够提供一个更加标准的、更加安全的方法来进行Base64的编码和解码操作,使得开发者们不再需要依赖外部库,Java 8 引入全新的 Base64,同时为了提供更好的性能,Java 为 Base64 的实现做了专门的性能优化。
Base64 的核心原理
在了解 Java 8 中 Base64 API 之前,我们先看 Base64 的实现原理。
Base64 的核心思想是将数据流的每三个字节划分为一组,总共24位,再将这24位分为4组,每组6位。由于每组现在只有6位,因此它可以表示的最大数值是 2^6 - 1 = 63
,Base64编码正是利用这64个数字(从0到63)对应到可打印字符的映射关系来工作的。
Base64 编码的步骤如下:
- 分组:输入数据被分成每组3字节(24位)。如果最后一组不足3字节,则用0填充至3字节。
- 映射:每组24位被进一步划分为4个6位的小组。每个6位小组将被映射为一个0-63之间的数字。
- 编码表:这些数字用作Base64编码表中的索引,该表由64个字符组成,包括大小写英文字母、数字和两个额外符号(通常是
+
和/
),还有一个用于填充的=
符号,以确保输出的字符数为4的倍数。 - 转换:每个6位的分组对应的数字转换成相应的Base64字符。
- 填充:如果原始数据字节长度不是3的倍数,最终的编码可能会用
=
字符填充至4的倍数长度,这样接收方在解码时能够恢复原始数据。
编码表如下:
数值 | 字符 | 数值 | 字符 | 数值 | 字符 | 数值 | 字符 |
---|---|---|---|---|---|---|---|
0 | A | 16 | Q | 32 | g | 48 | w |
1 | B | 17 | R | 33 | h | 49 | x |
2 | C | 18 | S | 34 | i | 50 | y |
3 | D | 19 | T | 35 | j | 51 | z |
4 | E | 20 | U | 36 | k | 52 | 0 |
5 | F | 21 | V | 37 | l | 53 | 1 |
6 | G | 22 | W | 38 | m | 54 | 2 |
7 | H | 23 | X | 39 | n | 55 | 3 |
8 | I | 24 | Y | 40 | o | 56 | 4 |
9 | J | 25 | Z | 41 | p | 57 | 5 |
10 | K | 26 | a | 42 | q | 58 | 6 |
11 | L | 27 | b | 43 | r | 59 | 7 |
12 | M | 28 | c | 44 | s | 60 | 8 |
13 | N | 29 | d | 45 | t | 61 | 9 |
14 | O | 30 | e | 46 | u | 62 | + |
15 | P | 31 | f | 47 | v | 63 | / |
有了这个映射表我们就把任意的二进制转换成Base64的编码了,下面大明哥举个例子给大家演示下转换过程。我们将 sikejava
字符串转换为 Base64 编码
步骤1:将字符转换为ASCII值
将每个字符转换为对应一个ASCII值。
s
->115
i
->105
k
->107
e
->101
j
->106
a
->97
v
->118
a
->97
步骤2:将ASCII值转换为二进制
将每个ASCII值转换为8位二进制数。
115
->01110011
105
->01101001
107
->01101011
101
->01100101
106
->01101010
97
->01100001
118
->01110110
97
->01100001
串连起来就是:01110011 01101001 01101011 01100101 01101010 01100001 01110110 01100001
步骤3:将二进制数据划分为6位一组
将连续的二进制位分成6位一组的小块。如果最后一组不足6位,需要用0填充。
上面二进制分为 6 位一组:011100 110110 100101 101011 011001 010110 101001 100001 011101 100110 000100
步骤 4:将6位二进制数转换为十进制
每组6位的二进制数将被转换成十进制数。
011100
->28
110110
->54
100101
->37
101011
->43
011001
->25
010110
->22
101001
->41
100001
->33
011101
->29
100110
->38
000100
->4
步骤5:将十进制数映射到Base64字符
28
->c
54
->2
37
->l
43
->r
25
->Z
22
->W
41
->p
33
->h
29
->d
38
->m
4
->E
所以,"sikejava
"对应的Base64编码是 c2lrZWphdmE=
。最后的=
符号用于填充,因为Base64编码的输出应该是4的倍数。
我们用代码验证下 :
System.out.println(Base64.getEncoder().encodeToString("sikejava".getBytes()));
// 结果......
c2lrZWphdmE=
Java 8 中的Base64 API
Java 8 中的 Base64 API 提供了三种主要类型的 Base64 编码和解码,他们分别适用于不同的场景和需求。
基本 Base64 编码和解码
- 编码器: 使用
Base64.getEncoder()
获取。 - 解码器: 使用
Base64.getDecoder()
获取。
它们提供了基本的 Base64 编码和解码功能,适用于所有需要 Base64 编码的场景。其特点是输出编码字符串不会包含用于换行的字符。
URL 和文件名安全 Base64 编码和解码
- 编码器: 使用
Base64.getUrlEncoder()
获取。 - 解码器: 使用
Base64.getUrlDecoder()
获取。
该编解码器适用于 URL 和文件名的 Base64 编码。由于 URL 中的某些字符(如 +
和 /
)有特殊含义,所以需要使用这种编码方式来替换这些字符。使用该编码器,编码输出中的 +
和 /
字符分别被替换为 -
和 _
,使得编码后的字符串可以安全地用在 URL 和文件名中。
MIME 类型 Base64 编码和解码
- 编码器: 使用
Base64.getMimeEncoder()
获取。 - 解码器: 使用
Base64.getMimeDecoder()
获取。
该编码器适用于 MIME 类型(如电子邮件)的内容,其中可能需要支持多行输出。特点是持按照 MIME 类型的要求将输出格式化为每行固定长度的多行字符串。默认每行长度不超过 76 个字符,并在每行后插入 \r\n
。
Base64 提供了多种编解码的方法,大明哥这里列举几个最常用的:
encode(byte[] src)
: 将给定的字节数组编码为 Base64 字符串。encodeToString(byte[] src)
: 将给定的字节数组编码为一个 Base64 字符串,并将其直接转换为字符串格式。decode(String src)
: 将给定的 Base64 字符串解码为字节数组。decode(byte[] src)
: 将给定的 Base64 编码的字节数组解码为原始字节数组。
Base64示例
基本 Base64 编码
基本编码是最常见的类型,适用于大多数需要 Base64 编码的场景。
@Test
public void base64BasicTest() {
String skStr = "skjava";
// 编码
String encodingStr = Base64.getEncoder().encodeToString(skStr.getBytes());
System.out.println("encodingStr = " + encodingStr);
// 解码
String decodeStr = new String(Base64.getDecoder().decode(encodingStr));
System.out.println("decodeStr = " + decodeStr);
}
// 结果......
encodingStr = c2tqYXZh
decodeStr = skjava
URL 和文件名安全 Base64 编码
URL和文件名安全编码会替换掉一些在 URL 中可能会引起问题的字符,比如 +
和 /
。
@Test
public void base64UrlTest() {
String skStr = "https://skjava.com/?series=skjava";
// 编码
String encodingStr = Base64.getUrlEncoder().encodeToString(skStr.getBytes());
System.out.println("encodingStr = " + encodingStr);
// 解码
String decodeStr = new String(Base64.getUrlDecoder().decode(encodingStr));
System.out.println("decodeStr = " + decodeStr);
}
// 结果......
encodingStr = aHR0cHM6Ly9za2phdmEuY29tLz9zZXJpZXM9c2tqYXZh
decodeStr = https://skjava.com/?series=skjava
MIME 类型 Base64 编码
MIME 类型编码适用于电子邮件或其他 MIME 类型的内容,它支持多行输出。
@Test
public void base64MimeTest() {
String skStr = "Hello, MIME-Type Example。\r\n" +
"sike-java,sike-java-feature;" +
"sike-javanio,sike-netty";
// 编码
String encodingStr = Base64.getMimeEncoder().encodeToString(skStr.getBytes());
System.out.println("encodingStr = " + encodingStr);
// 解码
String decodeStr = new String(Base64.getMimeDecoder().decode(encodingStr));
System.out.println("decodeStr = " + decodeStr);
}
// 结果......
encodingStr = SGVsbG8sIE1JTUUtVHlwZSBFeGFtcGxl44CCDQpzaWtlLWphdmEsc2lrZS1qYXZhLWZlYXR1cmU7
c2lrZS1qYXZhbmlvLHNpa2UtbmV0dHk=
decodeStr = Hello, MIME-Type Example。
sike-java,sike-java-feature;sike-javanio,sike-netty
大明哥花了两个月时间终于写完了 Java 8 ~ Java 21 所有的重要特性,整个系列共 63 篇文章,11w+ 字。
现在终于将其整理成了 PDF 版本,同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【Java 新特性】 即可免费领取。