一、Base64是什么
Base64是一种用于将二进制数据编码为ASCII字符的编码方式,主要目的是为了能够在文本环境中传输和存储二进制数据。这种编码方式广泛应用于电子邮件、HTTP协议和其他需要传输或存储二进制数据的地方。
二、发明Base64编码的原因
Base64编码的发明解决了在文本环境中传输和存储二进制数据的一系列问题,通过将二进制数据转换为可打印的ASCII字符,提高了数据传输的兼容性、安全性和可靠性。
以下是几点原因及其说明:
1、兼容性:
- 文本系统的限制:许多应用系统(例如早期的电子邮件系统和某些协议)只能处理ASCII字符集,不能直接处理二进制数据。Base64通过将二进制数据转换为可打印的ASCII字符,使其能够在这些系统中传输和存储。
- 避免数据损坏:在传输过程中,有些字符可能会被误解释为控制字符,从而导致数据损坏。Base64通过使用可打印的字符避免了这个问题。
2、标准化
- 通用性:Base64提供了一种标准化的方法来编码和解码二进制数据,使得不同系统和应用程序之间可以互操作,而不需要担心底层数据格式的差异。
- 广泛支持:Base64已经成为许多协议和文件格式的组成部分,并且得到了广泛的支持。例如,JSON Web Tokens(JWT)使用Base64来编码其负载部分。
3、易读性
- 可打印字符:Base64编码后的数据仅包含字母、数字、“+”和“/”,以及填充字符“=”。这些字符都是可打印的,这使得编码后的数据更容易阅读和调试。
- 避免特殊字符:Base64编码避免了使用可能在某些环境中有特殊含义的字符,例如空格、换行符和其他控制字符。
4、数据完整性
- 减少变动风险:由于Base64编码后的字符串不包含非打印字符或控制字符,因此在传输或存储过程中,不太可能因为格式转换等原因导致数据变动或丢失。
5、实现简单
- 编码和解码算法简单:Base64的编码和解码算法相对简单,可以很容易地在各种编程语言中实现。
三、Base64编码的编码原理
首先,准备一个包含64个ASCII码的字符数组:['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/'],这64个字符不一定需要一样,但都是ASCII码表里可见字符(关于ASCII码表可参考:ASCII码表介绍),如下:
private static final char[] toBase64 = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
// 由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以把字符+和/分别变成-和_:
private static final char[] toBase64URL = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
};
接着,对二进制数据进行分割重组。因为一个字节是8位(bit)二进制组成的,共有2^9个数值(0-255),而我们上面定义的字符数组是64位的,所以需要重组成每6位(bit)二进制为一组。
如上图,是3个字节(3x8=24bit)分割重组为4组(4x6=24bit),然后每一组的6位二进制可以转成一个数字(0-64,如,000000为0、000010为2等),这样我们得到4个数字作为索引,然后在上面定义的字符数组里获得相应的4个字符,就是编码后的字符串。
例如,上面n1-n4分别为:000000、000010、000100、111111,则索引分别为:0、2、4、63,编码后字符串为ACE/。
如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节,Base64编码会在末尾补上2个或1个\x00字节(二进制00000000),使其达到3个字节(24bit),可以分为4组(4x6=24bit),再在编码后字符串的末尾加上2个或1个=
号,表示补了多少字节,解码的时候,会自动去掉相应的\x00(其实有时候=号缺失也没事,程序会自动判断,取到足够数量二进制进行解码)。
例如,要编码的二进制为1个字节,00100001(33,英文!号),分组后n1-n4分别为:001000、010000、000000、000000,则索引为8(2^3)、16(2^1),一共补上2个字节(16bit),所以编码后的字符串为IQ==。
import java.util.Base64;
public static void main(String[] args) throws Exception {
System.out.println(String.format("%8s",Integer.toBinaryString("!".charAt(0))).replace(" ","0"));
byte[] b = {33};
System.out.println(new String(Base64.getEncoder().encode(b)));
System.out.println(new String(Base64.getDecoder().decode("IQ==".getBytes(StandardCharsets.UTF_8))));
}
// 打印结果
二进制数据:00100001
编码后字符串:IQ==
解码后字符串:!
所以,Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,好处是编码后的文本数据可以在邮件正文、网页等直接显示。
四、Base64编码的解码原理
解码原理就是反过来,字符串(如:ACE/)根据定义的ASCII码字符数组,找到各自索引,转为N组二进制(6bit/组),再分割成M个字节(8bit/字节),末尾有2个或1个=号的,会相应减去对应数量的字节,最后解码出M-2或M-1个字节。
五、Java下Base64的编码与解码
1、sun.misc下的BASE64Encoder和BASE64Decoder
优缺点:
- 这是JDK中自带的BASE64工具;
- 但是所提供的Base64功能编码和解码的效率并不太高,而且在JDK1.9以后就不被维护了。
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class Base64Test {
private static final BASE64Encoder ENCODE_64 = new BASE64Encoder();
private static final BASE64Decoder DECODE_64 = new BASE64Decoder();
@Test
public void sun_misc_base64_T() {
String text = "需要编码的数据";
try {
// 编码
String encodedStr = ENCODE_64.encodeBuffer(text.getBytes("UTF-8"));
System.out.println("encodedStr = " + encodedStr);
// 解码
String decodeStr = new String(DECODE_64.decodeBuffer(encodedStr), "UTF-8");
System.out.println("decodeStr = " + decodeStr);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、org.apache.commons.codec.binary下的Base64
优缺点:
- 与sun.misc方式比较,它的效率更高,代码更简洁;
- 所有的JDK版本都支持
import org.apache.commons.codec.binary.Base64;
public class Base64Test {
private static final Base64 BASE_64 = new Base64();
@Test
public void sun_misc_base64_T() {
String text = "需要编码的数据";
try {
// 编码
String encodedStr = BASE_64.encodeToString(text.getBytes("UTF-8"));
System.out.println("encodedStr = " + encodedStr);
// 解码
String decodeStr = new String(BASE_64.decode(encodedStr), "UTF-8");
System.out.println("decodeStr = " + decodeStr);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、java.util下的Base64
优缺点:
- java.util提供的Base64拥有更好的效能,实际测试编码与解码速度的话,比sun.misc套件提供的快至少11倍,比org.apache.commons.codec.binary提供的快至少3倍;
- 但是,需要JDK1.8以后的版本才支持。
import org.junit.Test;
import java.util.Base64;
public class Base64Test {
private static final Base64.Decoder DECODE_64 = Base64.getDecoder();
private static final Base64.Encoder ENCODE_64 = Base64.getEncoder();
@Test
public void sun_misc_base64_T() {
String text = "需要编码的数据";
try {
// 编码
String encodedStr = ENCODE_64.encodeToString(text.getBytes("UTF-8"));
System.out.println("encodedStr = " + encodedStr);
// 解码
String decodeStr = new String(DECODE_64.decode(encodedStr), "UTF-8");
System.out.println("decodeStr = " + decodeStr);
} catch (Exception e) {
e.printStackTrace();
}
}
}