Base64 是一种编码方式,不是加密算法,他的实现原理比较简单,我们知道一个字节占有 8 位,但在 ASCII 码中会有一些特殊的字符,而 Base64 就是每 6 位进行截取, 6 位二进制可以表示 64 个不同的状态,这 64 个状态可以用 64 个不同的字符来表示,常见的有英文小写字母 26 个,大写字母 26 个,数字 0 到 9 ,总共加起来有 62 个,还差两个可以用 ‘+’ , ‘/’ 来表示,如下图所示。每 6 位进行截取的时候,有可能不能被 6 整除,不能整除的时候在后面补 0 ,如果 6 位全是补的 0 ,为了防止混淆,需要再用一个字符来表示,比如 ‘=’ 。
编码的时候首先需要先把字符串转换为字节数组,每 3 个字节一组进行截取,因为每个字节占有 8 位, 3 个字节正好是 24 位,能被 6 整除,如果不足 3 个字节需要凑够 3 个,也就是在后面补 0 ,我们画个图看下 Base64 是怎么编码的。
1,原字符串是 “suanfa” ,长度为 6 ,是 3 的倍数。
2,原字符串是 “shuai” ,长度为 5 ,不是 3 的倍数,余 2 。
3,原字符串是 “boge” ,长度为 4 ,不是 3 的倍数,余 1 。
来看下代码:
// 字符怎么定义都可以。
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', '+', '/'
};
private static String encodeToString(String str) {
byte[] srcByte = str.getBytes();// 先把字符串转换成字节数组。
int srcLength = srcByte.length;// 原字节的长度。
int dstLength = ((srcLength + 2) / 3) << 2;// 编码之后的字节最大长度。
byte[] dstByte = new byte[dstLength];// 编码之后的字节数组。
char[] base64 = toBase64;
int srcIndex = 0;// 原字节数组的下标。
int desIndex = 0;// 转换之后的字节数组下标。
// 每 3 个字节一组截取,后面如果有不够 3 个的会补 0 ,然后单独计算。
int preLength = srcLength / 3 * 3;
int bits;
// 每 3 个字节转成 4 个。
while (srcIndex < preLength) {
// 每 3 个字节截取,每个字节 8 位,总共 24 位,byte 有负数,这里和 0xff 运算是截取。
bits = (srcByte[srcIndex++] & 0xff) << 16 |
(srcByte[srcIndex++] & 0xff) << 8 |
(srcByte[srcIndex++] & 0xff);
// 分成 4 份,每 6 位一份。
dstByte[desIndex++] = (byte) base64[(bits >>> 18) & 0x3f];
dstByte[desIndex++] = (byte) base64[(bits >>> 12) & 0x3f];
dstByte[desIndex++] = (byte) base64[(bits >>> 6) & 0x3f];
dstByte[desIndex++] = (byte) base64[bits & 0x3f];
}
// 因为每 3 个字节截取,如果有剩余的,要么剩余 1 个要么剩余 2 个。
if (srcIndex < srcLength) {
if (srcIndex + 1 == srcLength) {// 如果剩余 1 个。
bits = srcByte[srcIndex++] & 0xff;
// 先截取 6 位。
dstByte[desIndex++] = (byte) base64[bits >> 2];
// 剩下 2 位在补 4 个 0 。
dstByte[desIndex++] = (byte) base64[(bits & 0x03) << 4];
dstByte[desIndex++] = '=';// 表示缺失的意思。
} else {// 如果剩余 2 个。
// 2 字节总有 16 位,每 6 位一组只能分 3 组。
bits = (srcByte[srcIndex++] & 0xff) << 8 |
(srcByte[srcIndex++] & 0xff);
// 先截取 6 位。
dstByte[desIndex++] = (byte) base64[(bits >>> 10) & 0x3f];
// 在截取 6 位。
dstByte[desIndex++] = (byte) base64[(bits >>> 4) & 0x3f];
// 剩下 4 位然后在补 2 个 0 凑够 6 位。
dstByte[desIndex++] = (byte) base64[(bits & 0x0F) << 2];
}
dstByte[desIndex++] = '=';// 最后一个一定是等号。
}
return new String(dstByte);
}
我们来测试下:
public static void main(String[] args) {
String[] strs = {"wansuanfa.com(玩算法),一个学习算法的网站", "博哥就是帅!", "跟着博哥学算法……"};
for (int i = 0; i < strs.length; i++)
System.out.println("编码之后的字符串:" + encodeToString(strs[i]));
}
打印结果如下:
编码之后的字符串:d2Fuc3VhbmZhLmNvbSjnjqnnrpfms5Up77yM5LiA5Liq5a2m5Lmg566X5rOV55qE572R56uZ
编码之后的字符串:5Y2a5ZOl5bCx5piv5biF77yB
编码之后的字符串:6Lef552A5Y2a5ZOl5a2m566X5rOV4oCm4oCm
有了编码,我们再来看下解码,解码的时候和编码正好相反,解码也是先把待转码的字符串转换成字节数组,然后每 4 个字节一组进行截取,这里要注意最后是否有等号,如果有等号要单独处理。
来看下代码:
public static String decodeToStr(String str) {
// 逆序转换,转码的时候是把 byte 数字转成对应的字母,解码的时候要
// 把的字母在转换为对应的 byte 数字。
int[] fromBase64 = new int[128];
for (int i = 0; i < toBase64.length; i++)
fromBase64[toBase64[i]] = i;
byte[] srcByte = str.getBytes();
int srcLength = srcByte.length;
int equalsSign = 0;// 等号的个数,最多只能有 2 个。
if (srcByte[srcLength - 1] == '=') {
equalsSign++;
if (srcByte[srcLength - 2] == '=')
equalsSign++;
}
int dstLength = (srcLength >> 2) * 3 - equalsSign;
byte[] dstByte = new byte[dstLength];
int bits = 0;
int srcIndex = 0;
int desIndex = 0;
int preLength = dstByte.length / 3 * 3;
while (desIndex < preLength) {
// 每 4 个字节一组,每个字节截取 6 位,总共 24 位。
bits = (fromBase64[srcByte[srcIndex++]]) << 18 |
(fromBase64[srcByte[srcIndex++]] & 0x3f) << 12 |
(fromBase64[srcByte[srcIndex++]] & 0x3f) << 6 |
(fromBase64[srcByte[srcIndex++]] & 0x3f);
// 24 位分成 3 份,每 8 位一份。
dstByte[desIndex++] = (byte) ((bits >>> 16) & 0xff);
dstByte[desIndex++] = (byte) ((bits >>> 8) & 0xff);
dstByte[desIndex++] = (byte) (bits & 0xff);
}
// 如果有等号,要么有 1 个等号,要么有 2 个等号,要单独处理。
if (srcIndex < srcLength) {
if (srcByte[srcLength - 2] == '=') {// 有 2 个等号。
bits = (fromBase64[srcByte[srcIndex++]] & 0x3f) << 2 |
(fromBase64[srcByte[srcIndex++]] & 0x3f) >> 4;
} else {// 有 1 个等号。
bits = (fromBase64[srcByte[srcIndex++]] & 0x3f) << 10 |
(fromBase64[srcByte[srcIndex++]] & 0x3f) << 4 |
(fromBase64[srcByte[srcIndex++]] & 0x3f) >> 2;
dstByte[desIndex++] = (byte) ((bits >>> 8) & 0xff);
}
dstByte[desIndex++] = (byte) (bits & 0xff);
}
return new String(dstByte);
}
我们来测试下:
public static void main(String[] args) {
String[] strs = {"wansuanfa.com(玩算法),一个学习算法的网站", "跟着博哥学算法……"};
for (int i = 0; i < strs.length; i++) {
System.out.println("编码之前的字符串:" + strs[i]);// 编码之前的字符串。
String encodeStr = encodeToString(strs[i]);// 编码。
System.out.println("编码之后的字符串:" + encodeStr);// 打印编码之后的结果。
// 打印解码之后的结果。
System.out.println("解码之后的字符串:" + decodeToStr(encodeStr));
System.out.println();// 换行。
}
}
再来看下打印结果:
编码之前的字符串:wansuanfa.com(玩算法),一个学习算法的网站
编码之后的字符串:d2Fuc3VhbmZhLmNvbSjnjqnnrpfms5Up77yM5LiA5Liq5a2m5Lmg566X5rOV55qE572R56uZ
解码之后的字符串:wansuanfa.com(玩算法),一个学习算法的网站
编码之前的字符串:跟着博哥学算法……
编码之后的字符串:6Lef552A5Y2a5ZOl5a2m566X5rOV4oCm4oCm
解码之后的字符串:跟着博哥学算法……
上面编码的 64 个字符你也可以随便定义,除了每 6 位一组截取以外,还可以每 4 位一组截取,这样的话就更简单了,因为一个字节是 8 位,能被 4 整除,所以不会出现补 0 的情况,而 4 个二进制位有 16 种状态,我们可以随便选择 16 个字符,来看下代码。
// 字符怎么定义都可以。
private static final char[] toBase16 = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P'};
// 编码。
private static String encodeToString(String str) {
byte[] srcByte = str.getBytes();
int srcLength = srcByte.length;
byte[] dstByte = new byte[srcLength << 1];
char[] base64 = toBase16;
int srcIndex = 0;
int desIndex = 0;
int bits;
while (srcIndex < srcLength) {
// 一个字节 8 位分成 2 部分。
bits = srcByte[srcIndex++];
dstByte[desIndex++] = (byte) base64[(bits >>> 4) & 0x0f];
dstByte[desIndex++] = (byte) base64[bits & 0x0f];
}
return new String(dstByte);
}
// 解码。
public static String decodeToStr(String str) {
int[] fromBase64 = new int[128];
for (int i = 0; i < toBase16.length; i++)
fromBase64[toBase16[i]] = i;
byte[] srcByte = str.getBytes();
int srcLength = srcByte.length;
int dstLength = srcLength >> 1;
byte[] dstByte = new byte[dstLength];
int bits = 0;
int srcIndex = 0;
int desIndex = 0;
while (desIndex < dstLength) {
// 每两个合并成一个。
bits = (fromBase64[srcByte[srcIndex++]] & 0x0f) << 4 |
(fromBase64[srcByte[srcIndex++]] & 0x0f);
dstByte[desIndex++] = (byte) (bits & 0xff);
}
return new String(dstByte);
}
我们来测试一下:
public static void main(String[] args) {
String[] strs = {"博哥就是帅!", "跟着博哥学算法……"};
for (int i = 0; i < strs.length; i++) {
System.out.println("编码之前的字符串:" + strs[i]);// 编码之前的字符串。
String encodeStr = encodeToString(strs[i]);// 编码。
System.out.println("编码之后的字符串:" + encodeStr);// 打印编码之后的结果。
// 打印解码之后的结果。
System.out.println("解码之后的字符串:" + decodeToStr(encodeStr));
System.out.println();// 换行。
}
}
看下运行结果:
编码之前的字符串:博哥就是帅!
编码之后的字符串:OFINJKOFJDKFOFLALBOGJIKPOFLIIFOPLMIB
解码之后的字符串:博哥就是帅!
编码之前的字符串:跟着博哥学算法……
编码之后的字符串:OILHJPOHJNIAOFINJKOFJDKFOFKNKGOHKOJHOGLDJFOCIAKGOCIAKG
解码之后的字符串:跟着博哥学算法……