Base64编码是一种将字节数据编码为字符串的编码,字节数据会被编码成由64个可打印ASCII字符组成的字符串,这64个字符包括大写字母A-Z, 小写字母a-z, 以及数字 0 -9再加上 + 和 / ,刚好64个字符。对应的字符表如下图:
base64编码的一个用途就是对http的头信息进行编码,由于http头信息使用ASCII编码,如果包含特殊字符可能会导致头信息解析异常,采用base64编码保证头信息只包含一些简单字符,提高了安全性。前端在显示图片元素时也经常会遇到base64编码的图片资源。
那么base64具体是如何进行编码的呢?
编码方式很简单,就是对目标字节中的每六个bit位表示为字母表中的某个字符,例如:
‘abc’对应的二进制字节为: 01100001 , 01100010 , 01100011 ; 每六位进行分组得到的结果如下:
011000, 010110, 001001, 100011,转化为10进制就是:24,22, 9, 35,根据上面的字母表得到各个数字对应的字符为:YWJj,所以abc最终会被编码为 ‘YWJj’。由于三个字节最终被编码成了四个字节的字符串,所以长度增加了1/3.
看着这里大家可能会有一个问题,假设原数据的字节长度不是3的倍数,就会有剩余的bit位不够6个字符,这是就会涉及到填充的问题,填充就是在原数据后面加上额外的冗余位,使数据的bit位长度刚好能被24(也就是3个字节)整除(6和8的最小公倍数)。具体的填充规则可以简单的表述为: 任何完全填充(不包含原始数据中的位) 的 6 位组都由特殊的第 65 个符号“=” 表示。 如果 6 位组是部分填充的, 就将填充位设置为 0(http权威指南)。
举个例子:假设在编码的过程中原数组有四个字节,这时就需要再填充2个字节。假设最后的两个bit为是10,填充后的数据的最后几位如下:
10 xxxx, xxxxxx,xxxxxx (x代表填充位)
根据前面描述的填充规则:第一个6位组对应的数字为 10 0000,后面两个由于时完全填充的,被编码为==,最终的结果的后三位就变成了‘g==’。
在浏览器端,可以调用全局的方法 atob 和 btoa 实现二进制字符与base64编码的字符之间的互相转化。下面给出一个base64编码前端实现的简单例子:
const table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';// 参数为待编码的字符串async function toBase64(str) {const blob = new Blob([str], { type: 'text/plain' });// 获取字节长度,也可以使用第三方库提供的同步方法// 这里为了演示简单const srcBuf = await blob.arrayBuffer();// 获取原数据的字节长度const length = blob.size;// 计算需要填充的位数const padLen = length % 3 === 1? 2: length % 3 === 2 ? 1 : 0;// 最终的长度const size = length + padLen;const arrBuf = new ArrayBuffer(size);const srcArray = new Uint8Array(srcBuf);const dstArray = new Uint8Array(arrBuf);for (let i = 0; i < size; i++) {if (i < length) {dstArray[i] = srcArray[i];} else {dstArray[i] = 0x00;}}let result = [];// 每次处理3个字节for (let i = 0; i < size; i += 3) {// 取出三个字节const a = dstArray[i];const b = dstArray[i + 1];const c = dstArray[i + 2];// 最后一组const isLast = i + 3 > length;console.log(padLen)result.push(a >> 2); // 第一个字节的前6位result.push(((a & 0b00000011) << 4) | (b >> 4)); // 第一个字节的后两位加上第二个字节的前四位// 第二个字节的后四位加上的三个字节的前两位if (!isLast || padLen === 0) {result.push(((b & 0b00001111) << 2) | (c >> 6));result.push(c & 0b00111111); // 第三个字节的后六位} else {if (padLen === 2) {result.push('=', '=');} else if (padLen === 1) {result.push(((b & 0b00001111) << 2) | (c >> 6), '=');}}}result = result.map(code => code === '=' ? '=' : table[code]).join('');return result;}
base64转二进制字节原理类似,有兴趣的童鞋可以自行尝试!!
实际的运行效果如下图: