Base64的优势
Base64是一种常见的编码方式,主要用于在不同系统之间以文本形式传输二进制数据。其优势如下:
- 文本传输:Base64将二进制数据转换为纯文本,因此可以安全地在文本协议中传输,例如在URL、JSON等数据格式中嵌入二进制数据。
- 可读性:Base64编码后的数据只包含常见字符(A-Z、a-z、0-9、+、/),不包含控制字符和特殊字符,增加了数据的可读性和可打印性。
- 不改变数据:Base64编码不会对原始数据进行加密或压缩,只是一种映射关系,因此不会导致数据丢失。
Base64 编码对照表
下面是一个对照表,注意二进制这一列始终只用到了6位,而不是一个字节8位,这其实就是后面要说的base64会导致数据膨胀4/3的根本原因。
| 字符 | Base64码值 | 二进制值 | 字符 | Base64码值 | 二进制值 |
|---|---|---|---|---|---|
| A | 0 | 000000 | h | 33 | 100001 |
| B | 1 | 000001 | i | 34 | 100010 |
| C | 2 | 000010 | j | 35 | 100011 |
| D | 3 | 000011 | k | 36 | 100100 |
| E | 4 | 000100 | l | 37 | 100101 |
| F | 5 | 000101 | m | 38 | 100110 |
| G | 6 | 000110 | n | 39 | 100111 |
| H | 7 | 000111 | o | 40 | 101000 |
| I | 8 | 001000 | p | 41 | 101001 |
| J | 9 | 001001 | q | 42 | 101010 |
| K | 10 | 001010 | r | 43 | 101011 |
| L | 11 | 001011 | s | 44 | 101100 |
| M | 12 | 001100 | t | 45 | 101101 |
| N | 13 | 001101 | u | 46 | 101110 |
| O | 14 | 001110 | v | 47 | 101111 |
| P | 15 | 001111 | w | 48 | 110000 |
| Q | 16 | 010000 | x | 49 | 110001 |
| R | 17 | 010001 | y | 50 | 110010 |
| S | 18 | 010010 | z | 51 | 110011 |
| T | 19 | 010011 | 0 | 52 | 110100 |
| U | 20 | 010100 | 1 | 53 | 110101 |
| V | 21 | 010101 | 2 | 54 | 110110 |
| W | 22 | 010110 | 3 | 55 | 110111 |
| X | 23 | 010111 | 4 | 56 | 111000 |
| Y | 24 | 011000 | 5 | 57 | 111001 |
| Z | 25 | 011001 | 6 | 58 | 111010 |
| a | 26 | 011010 | 7 | 59 | 111011 |
| b | 27 | 011011 | 8 | 60 | 111100 |
| c | 28 | 011100 | 9 | 61 | 111101 |
| d | 29 | 011101 | + | 62 | 111110 |
| e | 30 | 011110 | / | 63 | 111111 |
| f | 31 | 011111 | = | (填充字符) | (填充字符) |
| g | 32 | 100000 |
编码逻辑
一张图看懂Base64算法是怎么把cat这个单词变成Base64字符串的
如上图
- cat这个单词需要用3个字节存储,分别是
011000110110000101110100, 8 * 3 = 24bit - 把这24bit重新拆分成 6 * 4 = 24bit
- 每6位为一组,转成10进制,到上面的对照表中找相应的字符;
- 分别找到了Y2F0
这样cat经过Base64编码后就变成了Y2F0,你会发现原来3个字符就可以表达的东西,现在要花四个字符才能表达,这就是数据转成Base64后传输更耗带宽的原因。
末尾补等号怎么回事?
因为8和6最小公倍数是24(3个字节),所以Base64编码时必须3个字节为一组,一组一组的处理。那么数据字节数就不是3的倍数怎么办呢?
如果最后剩下 1 个字节,那么将补 4 个 0 位,编码成 2 个 Base64 字符,然后补两个 =:
如果最后剩下 2 个字节,那么将补 2 个 0 位,编码成 3 个 Base64 字符,然后补一个 =:
代码举例
下面举一个例子,将“我是小菜鸡”这几个字用Base64编码一下
function stringToBase64(str) {
// Base64字符集
const base64Chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let result = "";
let i = 0;
// 遍历输入字符串
while (i < str.length) {
const char1 = str.charCodeAt(i++);
const char2 = str.charCodeAt(i++);
const char3 = str.charCodeAt(i++);
// 将字符转换为Base64编码的索引值
const enc1 = char1 >> 2; // 取前6位
const enc2 = ((char1 & 3) << 4) | (char2 >> 4); // 取后2位 + 下一个字符的前4位
const enc3 = ((char2 & 15) << 2) | (char3 >> 6); // 取后4位 + 下一个字符的前2位
const enc4 = char3 & 63; // 取后6位
// 处理字符是否是末尾字符,并加入到结果中
if (isNaN(char2)) {
enc3 = enc4 = 64;
} else if (isNaN(char3)) {
enc4 = 64;
}
// 根据Base64字符集,将编码后的索引值转换为Base64字符,并拼接到结果中
result +=
base64Chars.charAt(enc1) +
base64Chars.charAt(enc2) +
base64Chars.charAt(enc3) +
base64Chars.charAt(enc4);
}
return result;
}
// 测试字符串
const text = "我是小菜鸡";
// 使用手动实现的Base64编码函数将字符串转换为Base64
const base64Text = stringToBase64(unescape(encodeURIComponent(text)));
console.log(base64Text); // 输出:5oiR5piv5bCP6I+c6bih
数据膨胀的原因
其实上面也提到了,这里总结一下:
- 编码字符集扩大:在原始数据中,每个字节有256个可能的取值(2^8)。而在Base64编码中,每个6位二进制数据只有64个可能的取值(2^6),因为Base64使用的字符集仅包含64个字符。因此,将原始数据中的8位二进制数据转换为6位Base64字符,必然导致数据大小的增加。
- 填充字符的引入:在Base64编码中,如果原始数据的字节长度不是3的倍数,会使用等号 "=" 进行填充,确保编码后的数据长度是4的倍数。填充字符会增加编码数据的大小。
为什么有Base64而没有Base256
聪明的你会发现,导致Base64编码后的大小是原来的4/3倍的根本原因是对照表里只有64个明文字符,最多只能表示64种可能,也就是二进制000000 ~ 111111,6个比特。如果对照表里有256个字符,不就可以表示00000000 ~ 11111111 了?就不会浪费了。Base256没有被发明,原因有以下几点:
- 可打印性和可读性: Base64编码使用的字符集由64个常见、可打印的ASCII字符组成(A-Z、a-z、0-9、+、/),这使得编码后的数据易于阅读和传输。相比之下,Base256需要使用ASCII字符集中所有的256个字符,其中包含许多不可打印的字符,这使得数据难以阅读和处理。试想一下如cat这个单词如果用base256编码,可能长这样
口口口口口,dog这个单词也可能是口口口口口。 - 传输和存储效率: 虽然Base64编码会导致数据膨胀,但在一些场景中,数据膨胀带来的额外开销可以被接受,而且Base64的可读性和通用性更有价值。相比之下,Base256的数据大小会更接近原始二进制数据,但在文本传输和存储方面并没有明显的优势。