网站的友情链接做多少个比较合适/百度免费推广网站
Base64由来:
Base64算法最早应用于解决电子邮件传输的问题。在早期,由于“历史问题”,电子邮件只允许ASCII码字符。如果要传输一封带有非ASCII码字符的电子邮件,当通过有“历史问题”的网关时就可能出现问题。这个网关很可能会对这个非ASCII码字符的二进制位做调整,即将这个非ASCII码的8位二进制码的最高位置为0。此时用户收到的邮件就会是一封纯粹的乱码邮件。基于这个原因产生了Base64算法。
Base64定义:
根据RFC2045、RFC4648规定,Base64由64个ASCII码字符组成,Base64目前有三种格式,RFC2045、RFC4648、RFC4648 URLSAFE。
RFC2045、RFC4648、RFC4648 URLSAFE区别:
· RFC2045和RFC4648中所用字符如表1,RFC2045规定,每76个字符为一行,每行末需添加一个回车换行符(\r\n)。
· RFC4648 URLSAFE中所用字符如表2,与RFC2045、RFC4648有两个字符不一样,+、/由-、替换,由于+和/在URL编码时,会出现丢失,所以提供了替换更为安全的-、。
Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
---|---|---|---|---|---|---|---|
0 | A | 17 | R | 34 | i | 51 | z |
1 | B | 18 | S | 35 | j | 52 | 0 |
2 | C | 19 | T | 36 | k | 53 | 1 |
3 | D | 20 | U | 37 | l | 54 | 2 |
4 | E | 21 | V | 38 | m | 55 | 3 |
5 | F | 22 | W | 39 | n | 56 | 4 |
6 | G | 23 | X | 40 | o | 57 | 5 |
7 | H | 24 | Y | 41 | p | 58 | 6 |
8 | I | 25 | Z | 42 | q | 59 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | L | 28 | c | 45 | t | 62 | + |
12 | M | 29 | d | 46 | u | 63 | / |
13 | N | 30 | e | 47 | v | padding | = |
14 | O | 31 | f | 48 | w | ||
15 | P | 32 | g | 49 | x | ||
16 | Q | 33 | h | 50 | y |
表 1
Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
---|---|---|---|---|---|---|---|
0 | A | 17 | R | 34 | i | 51 | z |
1 | B | 18 | S | 35 | j | 52 | 0 |
2 | C | 19 | T | 36 | k | 53 | 1 |
3 | D | 20 | U | 37 | l | 54 | 2 |
4 | E | 21 | V | 38 | m | 55 | 3 |
5 | F | 22 | W | 39 | n | 56 | 4 |
6 | G | 23 | X | 40 | o | 57 | 5 |
7 | H | 24 | Y | 41 | p | 58 | 6 |
8 | I | 25 | Z | 42 | q | 59 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | L | 28 | c | 45 | t | 62 | - |
12 | M | 29 | d | 46 | u | 63 | _ |
13 | N | 30 | e | 47 | v | padding | = |
14 | O | 31 | f | 48 | w | ||
15 | P | 32 | g | 49 | x | ||
16 | Q | 33 | h | 50 | y |
表 2
演示示例:
JDK1.8版本开始,JDK默认提供Base64工具类,即java.util.Base64,其中对RFC2045、RFC4648、RFC4648 URLSAFE格式加解码,示例如下:
package com.securitit.serialize.bs64;import java.util.Base64;public class Base64Tester {public static void main(String[] args) throws Exception {String plainStr = null;String bs64Str = null;byte[] plainBts = null;// 原文内容.plainStr = "Hello Base64!Now this is testing RFC2045 Base64!Please see the result!";// RFC2045测试.bs64Str = Base64.getMimeEncoder().encodeToString(plainStr.getBytes("UTF-8"));System.out.println("RFC2045-Base64编码结果:");System.out.println(bs64Str);plainBts = Base64.getMimeDecoder().decode(bs64Str);plainStr = new String(plainBts, "UTF-8");System.out.println("RFC2045-Base64解码结果:");System.out.println(plainStr);System.out.println("===============================================================");// RFC4648测试.bs64Str = Base64.getEncoder().encodeToString(plainStr.getBytes("UTF-8"));System.out.println("RFC4648-Base64编码结果:");System.out.println(bs64Str);plainBts = Base64.getDecoder().decode(bs64Str);plainStr = new String(plainBts, "UTF-8");System.out.println("RFC4648-Base64解码结果:");System.out.println(plainStr);System.out.println("===============================================================");// RFC4648 URLSAFE测试.bs64Str = Base64.getUrlEncoder().encodeToString(plainStr.getBytes("UTF-8"));System.out.println("RFC4648 URLSAFE-Base64编码结果:");System.out.println(bs64Str);plainBts = Base64.getUrlDecoder().decode(bs64Str);plainStr = new String(plainBts, "UTF-8");System.out.println("RFC4648 URLSAFE-Base64解码结果:");System.out.println(plainStr);}}
输出结果:
RFC2045-Base64编码结果:
SGVsbG8gQmFzZTY077yBTm93IHRoaXMgaXMgdGVzdGluZyBSRkMyMDQ1IEJhc2U2NO+8gVBsZWFz
ZSBzZWUgdGhlIHJlc3VsdO+8gQ==
RFC2045-Base64解码结果:
Hello Base64!Now this is testing RFC2045 Base64!Please see the result!
===============================================================
RFC4648-Base64编码结果:
SGVsbG8gQmFzZTY077yBTm93IHRoaXMgaXMgdGVzdGluZyBSRkMyMDQ1IEJhc2U2NO+8gVBsZWFzZSBzZWUgdGhlIHJlc3VsdO+8gQ==
RFC4648-Base64解码结果:
Hello Base64!Now this is testing RFC2045 Base64!Please see the result!
===============================================================
RFC4648 URLSAFE-Base64编码结果:
SGVsbG8gQmFzZTY077yBTm93IHRoaXMgaXMgdGVzdGluZyBSRkMyMDQ1IEJhc2U2NO-8gVBsZWFzZSBzZWUgdGhlIHJlc3VsdO-8gQ==
RFC4648 URLSAFE-Base64解码结果:
Hello Base64!Now this is testing RFC2045 Base64!Please see the result!
从输出可以很明显的看出各种格式的区别,每种数据格式都对应某种应用场景。RFC2045较合适邮件传输,RFC4648较适合针对文件、大数据的编码,RFC4648 URLSAFE较适合需要在URL中传输的数据。
源码分析:
Base64编码是将3个字节转换为4个字节,然后使用ASCII表示,大致过程如下图所示:
当3个字节转换为二进制后变为24位二进制,这24位二进制均分后变为4个字节,每个字节只有低6位是有效的,低6位的值范围是0-63,正好是本文开头的字典数据。
接下来,我们以JDK1.8的java.util.Base64为例,对源码进行简单分析。
实现基础:
实现基础就是本文开头两个字典,按照上图中的过程,转换后按照字典取值,拼接为字符串,即是Base64编码后的值。
基本方法:
java.util.Base64的源码相对比较简单,我们只对其中两个比较重要的方法encode0和decode0进行分析,这两个方法是编码和解码的主要方法,其他方法都是围绕这两个方法进行的简单封装和调用,在此不做赘述。
encode0:
encode0方法负责编码。
private int encode0(byte[] src, int off, int end, byte[] dst) {// 取得编码字典.char[] base64 = isURL ? toBase64URL : toBase64;// 源数据初始位置.int sp = off;// 对字节数组进行分组,三个字节为一组,计算可分组数量.int slen = (end - off) / 3 * 3;// 可完整的分组的数量.int sl = off + slen;// 若限定每行最大字符数,且最大分组数大于每行最大字符数对应的最大分组数.if (linemax > 0 && slen > linemax / 4 * 3)// 使用每行最大字符数计算最大分组数.slen = linemax / 4 * 3;int dp = 0;// 处理源数据,直到数据处理完为止.while (sp < sl) {// 若未处理完,则s10为当前处理为止.int sl0 = Math.min(sp + slen, sl);for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {// 取3个字节的低8位,组成一个23位的int.int bits = (src[sp0++] & 0xff) << 16 |(src[sp0++] & 0xff) << 8 |(src[sp0++] & 0xff);// 0x3f二进制是0011 1111// 下面四个语句,针对24位,每6位与0x3f进行&操作,结果作为一个字节.dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f];dst[dp0++] = (byte)base64[bits & 0x3f];}// 一次处理过后,对数据进行初始化.int dlen = (sl0 - sp) / 3 * 4;dp += dlen;sp = sl0;// CRF2045协议处理.if (dlen == linemax && sp < end) {for (byte b : newline){dst[dp++] = b;}}}// 对剩余的未到3个的字节处理.if (sp < end) { // 1 or 2 leftover bytesint b0 = src[sp++] & 0xff;dst[dp++] = (byte)base64[b0 >> 2];if (sp == end) {dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];// 若需要补位,则用=进行补位.if (doPadding) {dst[dp++] = '=';dst[dp++] = '=';}} else {// 取数据低8位.int b1 = src[sp++] & 0xff;dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];// 若需要补位,则用=进行补位.if (doPadding) {dst[dp++] = '=';}}}return dp;
}
decode0:
decode0方法负责解码。
private int decode0(byte[] src, int sp, int sl, byte[] dst) {// 是RFC4648或RFC4648 URLSAFE.int[] base64 = isURL ? fromBase64URL : fromBase64;int dp = 0;int bits = 0;// 处理4个字节时,首个字节移动位数.int shiftto = 18;// 遍历源数据,直至源数据遍历结束.while (sp < sl) {// 逐个获取字节低8位.int b = src[sp++] & 0xff;// 低8位在字典中数值小于0时.if ((b = base64[b]) < 0) {// 包含=的填充.if (b == -2) {// = shiftto==18 unnecessary padding// x= shiftto==12 a dangling single x// x // xx= shiftto==6&&sp==sl missing last =// xx=y shiftto==6 last is not =// 输入字节数组有错误的4字节结束单元.// 移位数量为18时,没必要填充.// 移位数量为12时,只有一个值,例如:x=.// 与无填充示例一起使用.// 移位数量为6时,且源数据已到末尾,丢失最后的=,// 移位数量为6时,最后一个字符不是=.if (shiftto == 6 && (sp == sl || src[sp++] != '=') ||shiftto == 18) {throw new IllegalArgumentException("Input byte array has wrong 4-byte ending unit");}break;}// 跳过RFC2045.if (isMIME) // skip if for rfc2045continue;// 其它情况进行异常处理.elsethrow new IllegalArgumentException("Illegal base64 character " +Integer.toString(src[sp - 1], 16));}// 拼接bits,将4个字节放一起.// 第一个字节左移18位.// 第二个字节左移12位.// 第三个字节左移6位.// 第四个字节不做移动.bits |= (b << shiftto);// 以6位差递减移位数量.shiftto -= 6;// 移位数量小于0时.if (shiftto < 0) {// 将拼接好的bits拆分为3个字节.dst[dp++] = (byte)(bits >> 16);dst[dp++] = (byte)(bits >> 8);dst[dp++] = (byte)(bits);// 恢复移位数量为18.shiftto = 18;// 4个字节拼接存储结果.bits = 0;}}// 当最后移位数量为6时,计算一个字节.if (shiftto == 6) {dst[dp++] = (byte)(bits >> 16);} else // 当最后移位数量为0时,计算两个字节.if (shiftto == 0) {dst[dp++] = (byte)(bits >> 16);dst[dp++] = (byte)(bits >> 8);} else // 当最后移位数量为12时,此时说明字节处理异常. if (shiftto == 12) {// dangling single "x", incorrectly encoded.throw new IllegalArgumentException("Last unit does not have enough valid bits");}// 未处理到结尾.while (sp < sl) {// 当是媒体类型. && 源数据指定位置字节对应字典数据小于0时.if (isMIME && base64[src[sp++]] < 0)continue;throw new IllegalArgumentException("Input byte array has incorrect ending byte at " + sp);}return dp;
}
除了上面提供的方法,还提供了EncOutputStream、DecIInputSream用来针对流数据进行编码和解码,虽然接口和实现存在差异,但整体思路大致类似,各位若想知道具体原理,可查看源码详细分析。
注:文中源码均来自于JDK1.8版本,不同版本间可能存在差异。
如果有哪里有不明白或不清楚的内容,欢迎留言哦!