自己动手实现Base64编码
2016年04月02日 00:00

什么是Base64编码

首先,我们知道,编码的目的一般是为了数据压缩和数据传输的需要。Base64编码的产生就是因为后者:有些网络传送渠道并不支持所有的字节的传输。Base64编码就是一种将二进制数据映射成一些可打印字符(或逆过来)的映射规则,具体的说是映射到[A-Za-z0-9+/]上。比如将字符"中国"映射成5Lit5Zu9,当然也可将图片或其他文件编码成类似上述的字符。

Base64编码的原理

其次,我们需要知道的是,在计算机的世界里,一切都是由01011...这样的二进制数组成的,包括一个字符串,一张图片或一些其他类型的文件。比如对于一个字符串"中",在Java里,我们可以调用"中".getBytes()来得到"中"对应的字节数组:[-28, -72, -83],再将字节数组中的每个数转换为二进制,连接起来便是其二进制表示了:1110 0100_1011 1000_1010 1101(为了便于阅读,用下滑线将每个字节分开。当然,采用不同的编码形式,得到的字节数组可能不同,此处采用的是utf-8编码)。对于文件,可以从其输入流读中取到。

因为字符集合[A-Za-z0-9+/]长度是64,可以与所有6位长的二进制数一一对应 ,于是Base64编码的做法就是,将待编码的所有二进制位每6位分为一组,然后将每组二进制数映射到字符集合中的一个字符,最后将这些映射的字符依次排列起来便得到了编码结果。当然,待编码的二进制位可能不能被6整除,即最终可能会剩余2个或4个二进制位不能构成一组,这时我们可以在其后添加4或2个0来凑成一组。

比如对于上述的字符串"中",其二进制为:1110 0100_1011 1000_1010 1101,我们每6位组成一组,得到:111001_001011_100010_101101,每组转换为十进制就是:57_11_34_45,在按顺序映射到字符集合[A-Za-z0-9+/]上,便得到其Base64编码为:5Lit。如果最后一组填充了0,则还需在编码后的字符串后加上=符号。

解码的过程就是编码过程的逆过程,这里不再赘述。

Base64编码的实现

下面给出Base64编码的实现(末尾未加=):

public static String base64Encode(String text) {
    if (text == null) {
        return null;
    }
    final String table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-";
    byte[] textBytes = text.getBytes();
    byte[] keyBytes = key.getBytes();
    int remain = 0, remainBitCount = 0;
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < textBytes.length; i++) {
        int b = textBytes[i] & 0xFF;
        // 获取b的高(6 - remainBitCount)位
        int hight = b >>> (2 + remainBitCount) & 0xFF;
        // 与上一轮的余留组合成一个字节
        int curr = remain << (6 - remainBitCount) | hight;
        // builder.append(table.charAt(curr));
        builder.append(table.charAt(curr));
        // 剩余b的低(2 + remainBitCount)位
        remain = hight << (2 + remainBitCount) ^ b;
        remainBitCount += 2;
        // 若剩余的位刚好为6位,则进行编码
        if (remainBitCount == 6) {
            builder.append(table.charAt(remain));
            remainBitCount = 0;
            remain = 0;
        }
    }
    if (remainBitCount != 0) {
        // 多出的位到末尾补0凑够6位
        builder.append(table.charAt(remain << (6 - remainBitCount)));
    }
    return builder.toString();
}
public static String base64Decode(String text) {
    if (text == null) {
         return text;
    }
    byte[] bs = new byte[text.length() * 6 / 8];
    int pos = 0, remain = 0, remainBitCount = 0;
    final String table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-";
    for (int i = 0; i < text.length(); i++) {
        int code = table.indexOf(text.charAt(i));
        if (remainBitCount + 6 < 8) {
            // 不足一个字节
            remain = remain << 6 | code;
            remainBitCount += 6;
        } else {
            // 足够一个字节,取code的高(8 - remainBitCount)位
            int hight = code >>> (remainBitCount - 2) & 0xFF;
            bs[pos] = (byte) (remain << (8 - remainBitCount) | hight);
            pos++;
            remainBitCount -= 2;
            // 保留剩余的位
            remain = hight << remainBitCount ^ code;
        }
    }
    return new String(bs);
}

End