Base64是最经常使用的编码之一,好比开发中用于传递参数、现代浏览器中的<img />标签直接经过Base64字符串来渲染图片以及用于邮件中等等。Base64编码在RFC2045中定义,它被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。javascript
目录[-]java
Base64是最经常使用的编码之一,好比开发中用于传递参数、现代浏览器中的<img />标签直接经过Base64字符串来渲染图片以及用于邮件中等等。Base64编码在RFC2045中定义,它被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。windows
咱们知道,任何数据在计算机中都是以二进制的方式存储的。一个字节为8位,一个字符在计算机中存储为一个或多个字节,好比英文字母、数字以及英文标点符号就是用一个 字节来存储的,一般称为ASCII码。而简体中文、繁体中文、日文以及韩文等都是用多字节来存储的,一般称为多字节字符。由于Base64编码是对字符串的编码表示进行处理的,不一样编码的字符串的Base64的结果是不一样的,因此咱们须要了解基本的字符编码知识。浏览器
计算机最开始只支持ASCII码,一个字符用一个字节表示,只用了低7位,最高位为0,所以总共有128个ASCII码,范围为0~127。后来为了支持多种地区的语言,各大组织机构和IT厂商开始发明它们本身的编码方案,以便弥补ASCII编码的不足,如GB2312编码、GBK编码和Big5编码等。但这些编码都只是针对局部地区或少数语言文字,没有办法表达全部的语言文字。并且这些不一样的编码之间并无任何联系,它们之间的转换须要经过查表来实现。测试
为了提升计算机的信息处理和交换功能,使得世界各国的文字都能在计算机中处理,从1984年起,ISO组织就开始研究制定一个全新的标准:通用多八位(即多字节)编码字符集(Universal Multiple-Octet Coded Character Set),简称UCS。标准的编号为:ISO 10646。这一标准为世界各类主要语言的字符(包括简体及繁体的中文字)及附加符号,编制统一的内码。this
统一码(Unicode)是Universal Code的缩写,是由另外一个叫“Unicode学术学会”(The Unicode Consortium)的机构制定的字符编码系统。Unicode与ISO 10646国际编码标准从内容上来讲是同步一致的。具体可参考:Unicode 。编码
ANSI不表明具体的编码,它是指本地编码。好比在简体版windows上它表示GB2312编码,在繁体版windows上它表示Big5编码,在日文操做系统上它表示JIS编码。因此若是您新建了个文本文件并保存为ANSI编码,那么您如今应该知道这个文件的编码为本地编码。spa
Unicode编码是和字符表一一映射的。好比56DE表明汉字'回',这种映射关系是固定不变的。通俗的说Unicode编码就是字符表的坐标,经过56DE就能找到汉字'回'。Unicode编码的实现包括UTF八、UTF1六、UTF32等等。操作系统
Unicode自己定义的就是每一个字符的数值,是字符和天然数的映射关系,而UTF-8或者UTF-16甚至UTF-32则定义了如何在字节流中断字,是计算机领域的概念。.net
经过上图咱们知道,UTF-8编码为变长的编码方式,占1~6个字节,可经过Unicode编码值的区间来判断,而且每一个组成UTF8字符的字节都是有规律可循的。本文只讨论UTF8和UTF16这两种编码。
UTF16编码使用固定的2个字节来存储。由于是多字节存储,因此它的存储方式分为2种:大端序和小端序。UTF16编码是Unicode最直接的实现方式,一般咱们在windows上新建文本文件后保存为Unicode编码,其实就是保存为UTF16编码。UTF16编码在windows上采用小端序的方式存储,如下我新建了个文本文件并保存为Unicode编码来测试,文件中只输入了一个汉字'回',以后我用Editplus打开它,切换到十六进制方式查看,如图所示:
咱们看到有4个字节,前2个字节FF FE是文件头,表示这是一个UTF16编码的文件,而DE 56则是'回'的UTF16编码的十六进制。咱们常用的JavaScript语言,它内部就是采用UTF16编码,而且它的存储方式为大端序,来看一个例子:
1 |
<script type= "text/javascript" > |
2 |
console.group( 'Test Unicode: ' ); |
3 |
console.log(( '回' .charCodeAt(0)).toString(16).toUpperCase()); |
4 |
</script> |
很明显跟刚才Editplus显示的不同,顺序是相反的,这是由于字节序不同。具体可参考:UTF-16 。
UTF8是采用变长的编码方式,为1~6个字节,但一般咱们只把它看做单字节或三字节的实现,由于其它状况实在少见。UTF8编码经过多个字节组合的方式来显示,这是计算机处理UTF8的机制,它是无字节序之分的,而且每一个字节都很是有规律,详见上图,在此再也不详述。
UTF16和UTF8之间的相互转换能够经过上图的转换表来实现,判断Unicode码所在的区间就能够获得这个字符是由几个字节所组成,以后经过移位来实现。咱们用汉字'回'来举一个转换的例子。
咱们已经知道汉字'回'的Unicode码是0x56DE,它介于U+00000800 – U+0000FFFF之间,因此它是用三个字节来表示的。
因此咱们须要将0x56DE这个双字节的值变为三字节的值,注意上图中的x部分,就是对应0x56DE的各位字节,若是您数一下x的个数,会发现恰好是16位。
从0x56DE中取出4位放在低位,并和二进制的1110结合,这就是第一个字节。从0x56DE中剩下的字节中取出6位放在低位,并和二进制的10结合,这就是第二个字节。第三个字节依照相似的方式实现。
为了让你们更好的理解,如下代码只是实现汉字'回'的转换,代码以下:
01 |
<script type= "text/javascript" > |
02 |
/** |
03 |
* 转换对照表 |
04 |
* U+00000000 – U+0000007F 0xxxxxxx |
05 |
* U+00000080 – U+000007FF 110xxxxx 10xxxxxx |
06 |
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx |
07 |
* U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
08 |
* U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
09 |
* U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
10 |
*/ |
11 |
/* |
12 |
* '回'的Unicode编码为:0x56DE,它介于U+00000800 – U+0000FFFF之间,因此它占用三个字节。 |
13 |
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx |
14 |
*/ |
15 |
var ucode = 0x56DE; |
16 |
// 1110xxxx |
17 |
var byte1 = 0xE0 | ((ucode >> 12) & 0x0F); |
18 |
// 10xxxxxx |
19 |
var byte2 = 0x80 | ((ucode >> 6) & 0x3F); |
20 |
// 10xxxxxx |
21 |
var byte3 = 0x80 | (ucode & 0x3F); |
22 |
var utf8 = String.fromCharCode(byte1) |
23 |
+ String.fromCharCode(byte2) |
24 |
+ String.fromCharCode(byte3); |
25 |
26 |
console.group( 'Test UTF16ToUTF8: ' ); |
27 |
console.log(utf8); |
28 |
console.groupEnd(); |
29 |
</script> |
输出的结果看起来像乱码,这是由于JavaScript不知道如何显示UTF8的字符。您或许会说输出不正常转换还有什么用,但您应该知道转换的目的还常常用于传输或API的须要。
这是UTF16转换到UTF8的逆转换,一样须要对照转换表来实现。仍是接上一个例子,咱们已经获得了汉字'回'的UTF8编码,这是三个字节的,咱们只须要按照转换表来转成双字节的,如图所示,咱们须要保留下全部的x。
代码以下:
01 |
<script type= "text/javascript" > |
02 |
/** |
03 |
* 转换对照表 |
04 |
* U+00000000 – U+0000007F 0xxxxxxx |
05 |
* U+00000080 – U+000007FF 110xxxxx 10xxxxxx |
06 |
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx |
07 |
* U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
08 |
* U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
09 |
* U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
10 |
*/ |
11 |
/* |
12 |
* '回'的Unicode编码为:0x56DE,它介于U+00000800 – U+0000FFFF之间,因此它占用三个字节。 |
13 |
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx |
14 |
*/ |
15 |
var ucode = 0x56DE; |
16 |
// 1110xxxx |
17 |
var byte1 = 0xE0 | ((ucode >> 12) & 0x0F); |
18 |
// 10xxxxxx |
19 |
var byte2 = 0x80 | ((ucode >> 6) & 0x3F); |
20 |
// 10xxxxxx |
21 |
var byte3 = 0x80 | (ucode & 0x3F); |
22 |
var utf8 = String.fromCharCode(byte1) |
23 |
+ String.fromCharCode(byte2) |
24 |
+ String.fromCharCode(byte3); |
25 |
26 |
console.group( 'Test UTF16ToUTF8: ' ); |
27 |
console.log(utf8); |
28 |
console.groupEnd(); |
29 |
/** ------------------------------------------------------------------------------------*/ |
30 |
// 由三个字节组成,因此分别取出 |
31 |
var c1 = utf8.charCodeAt(0); |
32 |
var c2 = utf8.charCodeAt(1); |
33 |
var c3 = utf8.charCodeAt(2); |
34 |
/* |
35 |
* 须要经过判断特定位的方式来转换,但这里是已知是三个字节,因此忽略判断,而是直接拿到全部的x,组成16位。 |
36 |
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx |
37 |
*/ |
38 |
// 丢弃第一个字节的高四位并和第二个字节的高四位组成一个字节 |
39 |
var b1 = (c1 << 4) | ((c2 >> 2) & 0x0F); |
40 |
// 同理第二个字节和第三个字节组合 |
41 |
var b2 = ((c2 & 0x03) << 6) | (c3 & 0x3F); |
42 |
// 将b1和b2组成16位 |
43 |
var ucode = ((b1 & 0x00FF) << 8) | b2; |
44 |
console.group( 'Test UTF8ToUTF16: ' ); |
45 |
console.log(ucode.toString(16).toUpperCase(), String.fromCharCode(ucode)); |
46 |
console.groupEnd(); |
47 |
</script> |
知道了转换规则,就很容易实现了。
Base64编码要求把3个8位字节(3*8=24)转化为4个6位的字节(4*6=24),以后在6位的前面补两个0,造成8位一个字节的形式。因为2的6次方为64,因此每6个位为一个单元,对应某个可打印字符。当原数据不是3的整数倍时,若是最后剩下两个输入数据,在编码结果后加1个“=;若是最后剩下一个输入数据,编码结果后加2个“=;若是没有剩下任何数据,就什么都不要加,这样才能够保证资料还原的正确性。
每6个单元高位补2个零造成的字节位于0~63之间,经过在转码表中查找对应的可打印字符。“=”用于填充。以下图所示为转码表。
具体可参考: Base64 。
解码是编码的逆过程,先看后面补了几个“=”号,最多只可能补2个“=”号。一个“=”至关于补了2个0,因此去掉后面补的0后,再按8位展开,便可还原。
以前已经详细讲解了整个过程,本文的例子都是采用UTF8编码的字符串做为Base64编码的基础。由于JavaScript内部是使用Unicode编码,因此须要有个转换过程,原理以前也详细讲解过并给出了示例,如下是代码实现:
001 |
<script type= "text/javascript" > |
002 |
/** |
003 |
* UTF16和UTF8转换对照表 |
004 |
* U+00000000 – U+0000007F 0xxxxxxx |
005 |
* U+00000080 – U+000007FF 110xxxxx 10xxxxxx |
006 |
* U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx |
007 |
* U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
008 |
* U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
009 |
* U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
010 |
*/ |
011 |
var Base64 = { |
012 |
// 转码表 |
013 |
table : [ |
014 |
'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , |
015 |
'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , |
016 |
'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , |
017 |
'Y' , 'Z' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , |
018 |
'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , |
019 |
'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , |
020 |
'w' , 'x' , 'y' , 'z' , '0' , '1' , '2' , '3' , |
021 |
'4' , '5' , '6' , '7' , '8' , '9' , '+' , '/' |
022 |
], |
023 |
UTF16ToUTF8 : function (str) { |
024 |
var res = [], len = str.length; |
025 |
for ( var i = 0; i < len; i++) { |
026 |
var code = str.charCodeAt(i); |
027 |
if (code > 0x0000 && code <= 0x007F) { |
028 |
// 单字节,这里并不考虑0x0000,由于它是空字节 |
029 |
// U+00000000 – U+0000007F 0xxxxxxx |
030 |
res.push(str.charAt(i)); |
031 |
} else if (code >= 0x0080 && code <= 0x07FF) { |
032 |
// 双字节 |
033 |
// U+00000080 – U+000007FF 110xxxxx 10xxxxxx |
034 |
// 110xxxxx |
035 |
var byte1 = 0xC0 | ((code >> 6) & 0x1F); |
036 |
// 10xxxxxx |
037 |
var byte2 = 0x80 | (code & 0x3F); |
038 |
res.push( |
039 |
String.fromCharCode(byte1), |
040 |
String.fromCharCode(byte2) |
041 |
); |
042 |
} else if (code >= 0x0800 && code <= 0xFFFF) { |
043 |
// 三字节 |
044 |
// U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx |
045 |
// 1110xxxx |
046 |
var byte1 = 0xE0 | ((code >> 12) & 0x0F); |
047 |
// 10xxxxxx |
048 |
var byte2 = 0x80 | ((code >> 6) & 0x3F); |
049 |
// 10xxxxxx |
050 |
var byte3 = 0x80 | (code & 0x3F); |
051 |
res.push( |
052 |
String.fromCharCode(byte1), |
053 |
String.fromCharCode(byte2), |
054 |
String.fromCharCode(byte3) |
055 |
); |
056 |
} else if (code >= 0x00010000 && code <= 0x001FFFFF) { |
057 |
// 四字节 |
058 |
// U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
059 |
} else if (code >= 0x00200000 && code <= 0x03FFFFFF) { |
060 |
// 五字节 |
061 |
// U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
062 |
} else /** if (code >= 0x04000000 && code <= 0x7FFFFFFF)*/ { |
063 |
// 六字节 |
064 |
// U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
065 |
} |
066 |
} |
067 |
068 |
return res.join( '' ); |
069 |
}, |
070 |
UTF8ToUTF16 : function (str) { |
071 |
var res = [], len = str.length; |
072 |
var i = 0; |
073 |
for ( var i = 0; i < len; i++) { |
074 |
var code = str.charCodeAt(i); |
075 |
// 对第一个字节进行判断 |
076 |
if (((code >> 7) & 0xFF) == 0x0) { |
077 |
// 单字节 |
078 |
// 0xxxxxxx |
079 |
res.push(str.charAt(i)); |
080 |
} else if (((code >> 5) & 0xFF) == 0x6) { |
081 |
// 双字节 |
082 |
// 110xxxxx 10xxxxxx |
083 |
var code2 = str.charCodeAt(++i); |
084 |
var byte1 = (code & 0x1F) << 6; |
085 |
var byte2 = code2 & 0x3F; |
086 |
var utf16 = byte1 | byte2; |
087 |
res.push(Sting.fromCharCode(utf16)); |
088 |
} else if (((code >> 4) & 0xFF) == 0xE) { |
089 |
// 三字节 |
090 |
// 1110xxxx 10xxxxxx 10xxxxxx |
091 |
var code2 = str.charCodeAt(++i); |
092 |
var code3 = str.charCodeAt(++i); |
093 |
var byte1 = (code << 4) | ((code2 >> 2) & 0x0F); |
094 |
var byte2 = ((code2 & 0x03) << 6) | (code3 & 0x3F); |
095 |
var utf16 = ((byte1 & 0x00FF) << 8) | byte2 |
096 |
res.push(String.fromCharCode(utf16)); |
097 |
} else if (((code >> 3) & 0xFF) == 0x1E) { |
098 |
// 四字节 |
099 |
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
100 |
} else if (((code >> 2) & 0xFF) == 0x3E) { |
101 |
// 五字节 |
102 |
// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
103 |
} else /** if (((code >> 1) & 0xFF) == 0x7E)*/ { |
104 |
// 六字节 |
105 |
// 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
106 |
} |
107 |
} |
108 |
109 |
return res.join( '' ); |
110 |
}, |
111 |
encode : function (str) { |
112 |
if (!str) { |
113 |
return '' ; |
114 |
} |
115 |
var utf8 = this .UTF16ToUTF8(str); // 转成UTF8 |
116 |
var i = 0; // 遍历索引 |
117 |
var len = utf8.length; |
118 |
var res = []; |
119 |
while (i < len) { |
120 |
var c1 = utf8.charCodeAt(i++) & 0xFF; |
121 |
res.push( this .table[c1 >> 2]); |
122 |
// 须要补2个= |
123 |
if (i == len) { |
124 |
res.push( this .table[(c1 & 0x3) << 4]); |
125 |
res.push( '==' ); |
126 |
break ; |
127 |
} |
128 |
var c2 = utf8.charCodeAt(i++); |
129 |
// 须要补1个= |
130 |
if (i == len) { |
131 |
res.push( this .table[((c1 & 0x3) << 4) | ((c2 >> 4) & 0x0F)]); |
132 |
res.push( this .table[(c2 & 0x0F) << 2]); |
133 |
res.push( '=' ); |
134 |
break ; |
135 |
} |
136 |
var c3 = utf8.charCodeAt(i++); |
137 |
res.push( this .table[((c1 & 0x3) << 4) | ((c2 >> 4) & 0x0F)]); |
138 |
res.push( this .table[((c2 & 0x0F) << 2) | ((c3 & 0xC0) >> 6)]); |
139 |
res.push( this .table[c3 & 0x3F]); |
140 |
} |
141 |
142 |
return res.join( '' ); |
143 |
}, |
144 |
decode : function (str) { |
145 |
if (!str) { |
146 |
return '' ; |
147 |
} |
148 |
149 |
var len = str.length; |
150 |
var i = 0; |
151 |
var res = []; |
152 |
153 |
while (i < len) { |
154 |
code1 = this .table.indexOf(str.charAt(i++)); |
155 |
code2 = this .table.indexOf(str.charAt(i++)); |
156 |
code3 = this .table.indexOf(str.charAt(i++)); |
157 |
code4 = this .table.indexOf(str.charAt(i++)); |
158 |
159 |
c1 = (code1 << 2) | (code2 >> 4); |
160 |
c2 = ((code2 & 0xF) << 4) | (code3 >> 2); |
161 |
c3 = ((code3 & 0x3) << 6) | code4; |
162 |
163 |
res.push(String.fromCharCode(c1)); |
164 |
165 |
if (code3 != 64) { |
166 |
res.push(String.fromCharCode(c2)); |
167 |
} |
168 |
if (code4 != 64) { |
169 |
res.push(String.fromCharCode(c3)); |
170 |
} |
171 |
172 |
} |
173 |
174 |
return this .UTF8ToUTF16(res.join( '' )); |
175 |
} |
176 |
}; |
177 |
178 |
console.group( 'Test Base64: ' ); |
179 |
var b64 = Base64.encode( 'Hello, oschina!又是一年春来到~' ); |
180 |
console.log(b64); |
181 |
console.log(Base64.decode(b64)); |
182 |
console.groupEnd(); |
183 |
</script> |
不得不说,在JavaScript中实现确实很麻烦。咱们来看下PHP对一样的字符串编码的结果:
由于字符编码是同样的,因此结果也同样。