base64了解一下
代码:https://github.com/374632897/demos/blob/master/encrypt/base64.js
在平时的工作中,base64使用场景比较多,比如文件上传。那么,base64到底是个什么东西?
通常来讲,我们平时使用的字符集是utf-8,而对于JavaScript中的binary string 而言,其使用的是ASCII扩展字符集。标准的ASCII字符集只有128个字符(2^7),而ASCII扩展字符集则包含了256个字符。
使用String.prototype.charCodeAt 方法,可以获取到相应字符的码点。
而base64,其字面意思就是使用64个字符来对输入内容进行描述(实际上应该是65个字符,A-Za-z0-9,以及+/总共64个字符,但是最后可能还需要使用=进行补位)。对于二进制字符串来说,可能包含256个字符,所以当使用base64来对二进制字符串进行编码的时候,就需要将256个字符描述的内容使用64个字符来进行描述。
简单来讲,就是把3个8bit的数据用4个6bit的数据来进行表示。
我们知道, 256 = 2 ^8,64 = 2 ^6, 因此当我们把二进制字符串的每个字符使用二进制来进行描述,并确保其填充到8位之后,再将相应的字符串以6位为单位进行分组,针对分组后的字符串再进行填充,填充到8位,这样就可以用64位字符编码来对其进行描述了。
在电子邮件中,每隔76个字符,还需要添加一个回车来进行换行。
以下以字符串“He”为模板进行步骤描述:
- 输入字符串 ‘He’
- 针对每个字符串,调用
String.prototype.charCodeAt
方法,获取其码点
const charsCode = "He".split("").map((item) => item.charCodeAt(0));
// [72, 101]
- 将获取到的码点用二进制来表示,并填充成8位
const binaryAry = charsCode.map((item) => item.toString(2));
// ["1001000", "1100101"]
// ["01001000", "01100101"]
- 将获得的二进制字符串数组进行合并并以6个为单位进行分组,在这里需要注意的是,如果合并后的二进制字符串不是3的整数的话,那么需要在末尾用0来进行填充
["01001000", "01100101"];
"0100100001100101".length; // 16
("010010000110010100"); // 末尾填充
// 010010 000110 010100
针对分组后的字符串,使用0将其填充到8位
// 010010 000110 010100
// 00010010 00000110 00010100
- 将最后得到的二进制字符串转换为十进制
// 010010 000110 010100
"010010 000110 010100".split(" ").map((item) => parseInt(item, 2));
// [18, 6, 20]
- 将得到的十进制数字从base64字母表中进行查找,得到对应的字符
a.map((item) => b64Ary[item]);
// ["S", "G", "U"]
- 判断得到的结果是否为4的倍数,如果不是,使用=号进行补位 或者说 原数据的长度是否为3的倍数,如果不是则补位(为1,则为两个==,为2则为1个=)
// ["S", "G", "U"]
// SGU
// SGU=
最后,转换完成,将 He 转换为了 SGU=
从上面的步骤来看,可能我们会有以下疑问:
- 第4步中为什么合并后的字符串需要是3的倍数?
因为需要将3个字节编码为4个字节
- 第8步中为什么最后的base64字符串需要为4的倍数?
同上,最后一段base64编码代表的是若干个字符,而编码规则便是以4为单位的
- 以上考虑的是针对二进制的,那么针对utf-8的字符串,要做base64处理的话,应该怎么做呢?
在使用base64编码后,编码后的数据相比于原数据会多出大概1/3的内容,因为原本的3个字符变成了4个字符,因此(4 - 3)/ 3 = 1/3 ** 另外一种实现方式:https://github.com/mathiasbynens/base64/blob/master/src/base64.js 带注释的版本
var encode = function (input) {
input = String(input);
if (/[^\0-\xFF]/.test(input)) {
// Note: no need to special-case astral symbols here, as surrogates are
// matched, and the input is supposed to only contain ASCII anyway.
error(
"The string to be encoded contains characters outside of the " +
"Latin1 range."
);
}
var padding = input.length % 3;
var output = "";
var position = -1;
var a;
var b;
var c;
var buffer;
// Make sure any padding is handled outside of the loop.
var length = input.length - padding;
while (++position < length) {
// Read three bytes, i.e. 24 bits.
a = input.charCodeAt(position) << 16;
b = input.charCodeAt(++position) << 8;
c = input.charCodeAt(++position);
buffer = a + b + c;
// Turn the 24 bits into four chunks of 6 bits each, and append the
// matching character for each of them to the output.
output +=
TABLE.charAt((buffer >> 18) & 0x3f) +
TABLE.charAt((buffer >> 12) & 0x3f) +
TABLE.charAt((buffer >> 6) & 0x3f) +
TABLE.charAt(buffer & 0x3f);
}
if (padding == 2) {
a = input.charCodeAt(position) << 8;
b = input.charCodeAt(++position);
buffer = a + b;
output +=
TABLE.charAt(buffer >> 10) +
TABLE.charAt((buffer >> 4) & 0x3f) +
TABLE.charAt((buffer << 2) & 0x3f) +
"=";
} else if (padding == 1) {
buffer = input.charCodeAt(position);
output +=
TABLE.charAt(buffer >> 2) + TABLE.charAt((buffer << 4) & 0x3f) + "==";
}
return output;
};
参考: