ASCII Unicode UTF-8 和 Base64

1 阅读8分钟

编码

字符 —> 二进制之间的映射

ASCII

  • 采用 ASCII 编码的字符,每个字符固定使用 1 个字节(byte/8bites)。因此,如果一个文本 只包含 ASCII 字符,那么它的字符数就等于字节数。

    hello 字节数就是 5

  • 将数字、英文、常见字符映射到 0-127 的数值空间里。

    其中最高 bit 位是 0,剩余 7 位用来真正的编码。故 2^7=128h 十进制的码值是 104,二进制码值就是:0110 1000

ASCII 码表

ASCII

GBK / ANSI

  • 双字节的编码,故最多能表示 2^16 = 65535个 字符。

    “GBK 映射为双字节” 只对中文、日文等非英文字符成立,对英文还是 1 字节。

Unicode

  • GBK 两个字节编码,只能表示 65535 个字符,全世界的文字字符总和远超这个数(汉语就有 55000 多字符),所以需要 3 个或 4 个甚至更多字节来表示一个字符。
  • 于是出现了 Unicode,为每一个有效字符定义的一个唯一的整数,称为码点 。‌这些码点通常使用十六进制来表示,并以 U+ 作为前缀。可以容纳 100 多万的字符(甚至包括表情包)。

    严格来讲,Unicode 是抽象映射(十六进制形式存储的字符集),不是一种编码方式。

length 的取值问题

字符:通常指的是 Unicode 的码点。 字素:视觉上认为的一个字。可能由多个码点组成。如:带组合音标的拉丁字母、国旗、emoji 码元:某种具体编码在内存里用的“最小存储块”。

  • UTF-16:一个码元是 16 位。JavaScript / Java / C# 等(UTF-16 字符串模型),不同语言标准不同。
  • str.length(JS):统计的是 UTF-16 码元个数,不是码点个数,更不是字素个数。
  • Unicode 把全部码点分成多个平面,其中 BMP(Basic Multilingual Plane,基本多文种平面) 内的码点通常 1 个 UTF-16 码元;增补平面(很多 emoji)通常需要 2 个 UTF-16 码元。 对于 BMP 字符,UTF-16 就是直接拿码点的二进制,无需拼凑;对于辅助平面,虽然要算代理对,但逻辑也不同于 UTF-8 的位分布。
  • 所以 js 中很多 emoji、𠮷 这类可能显示为 2。

UTF-8

  • 一个字符就需要 3 个字节表示,对于英文这样一个只需 1 个字节就可以表示的,太浪费了,于是需要对 Unicode 进行“压缩”编码,于是就有了 UTF-8、UTF-16、UTF-32 等编码。
  • UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用 1~4 个字节表示一个符号,根据不同的符号而变化字节长度。有效利用存储空间,避免浪费。
AUnicode 码点UTF-8UTF-16
十六进制U+00410x410x0041
十进制656565
十进制计算方式'A'.codePointAt(0) new TextEncoder().encode("A")[0]'A'.charCodeAt(0)
二进制0000 0000 0100 00010100 00010000 0000 0100 0001

UTF-8的编码规则:

  1. 对于单字节的符号,字节的最高位设为 0,后面 7 位为这个符号的 Unicode 码点的二进制。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  2. 对于 n(n>1)字节的符号,第一个字节的前 n 位都设为 1,第 n+1 位设为 0,后面字节的前两位一律设为 10。剩下的没有提及的二进制位,由这个符号的二进制进行填充。

下表总结了编码规则,字母x表示可用编码的位

Unicode 符号范围 (十六进制)UTF-8 编码方式(二进制)
0000 0000-0000 007F0xxxxxxx(和 ASCII 编码一致)
0000 0080-0000 07FF110xxxxx 10xxxxxx
0000 0800-0000 FFFF1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  • 以汉字 为例,已知 Unicode 码点是 4E25(1001110 00100101),根据上表,4E25 处在第三行的范围内 0000 0800-0000 FFFF,对应的 UTF-8 编码需要三个字节,即格式是 1110xxxx 10xxxxxx 10xxxxxx
  • 接着从 的最后一个二进制位开始,依次从后向前填入格式中的 x,空位补 0。
  • 然后就得到了, 的 UTF-8 编码是 11100100 10111000 10100101,转换成十六进制就是 E4 B8 A5

Base64编码

编码原理:

  1. 首先需要将字符转换为二进制,因此 Base64 的结果完全依赖于中间选择的字符编码方式。
  2. 将构成编码对象的二进制进行拼接起来,按 3 字节(24bit) 一组处理。
  3. 再拆成 4 组 6 bit。若最后字节不足 6 bit 位,用 0 补。
  4. 转换成十进制,然后对应 Base64 码表找到对应的字符。

    用 6 个 bit 位来表示一个字节,2^6=64。包含:大小写字母(52)、数字(10)、 +/

  5. 若最终 Base64 字符数量不是 4 的倍数,再用 = 补齐。
模拟实现
const base64Chars =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

function base64Encode(str) {
  // 常用 UTF-8 编码,不会造成空间浪费
  const bytes = new TextEncoder().encode(str); // [ 228, 184, 150, 74 ] 其中 世 占三个字节,J 占一个字节
  let binary = "";
  for (const b of bytes) binary += b.toString(2).padStart(8, "0");

  let result = "";
  for (let i = 0; i < binary.length; i += 6) {
    let chunk = binary.slice(i, i + 6);
    if (chunk.length < 6) chunk = chunk.padEnd(6, "0");
    result += base64Chars[parseInt(chunk, 2)];
  }

  const pad = (4 - (result.length % 4)) % 4;
  return result + "=".repeat(pad);
}
const char = "世J";
console.log(base64Encode(char)); // 5LiWSg==

Base64 的优点和缺点:

  • 优点
  1. Base64 编码可以将任意的二进制数据转换成 ASCII 可表示的字符集(纯文本格式),在数据传输和存储中有重要作用;

    可安全跨系统传输、且不容易冲突的可打印字符远没有 256 个;会包含大量控制字符/特殊字节,邮件、URL、JSON、HTTP 头等场景容易出问题。 Base64 选了 64 个安全字符,接受约 33% 膨胀来换兼容性。 8bit 那套更像“原始字节流”而不是“安全文本编码”。

  2. 减少服务器访问次数

    以前(HTTP/1.1)这点较明显; 现在 HTTP/2/HTTP/3 多路复用后,这个优势相对变小; 内联 Base64 仍可用于“小图标、避免额外请求”的场景。

为什么 base64 可以是文本,原始字节的是二进制流?

  • 关键不在 “6bit/8bit” 本身,而在你把这些 bit 映射成什么。

  • Base64:把数据映射到一组 “可打印字符”(A-Z a-z 0-9 + / =)。这些字符在 UTF-8/ASCII 里都有明确文本表示,所以看起来就是文本字符串,可放进 JSON、HTML、邮件正文。

  • 原始 8bit 字节:每个字节可取 0~255,里面大量值对应控制字节或不可打印内容。不一定能按文本编码正确解释,常会乱码/截断/协议冲突,所以通常按“二进制流”处理。未经文本编码约束的字节集合在工程上更适合作为流。

  • 缺点

  1. Base64 编码后通常约增加 33%(原来3 字节原始数据 → 4 字节 Base64 文本)。

    落盘/内存里每个 Base64 字符一般是 8bit。 数据越大,整体越接近 +33%,小数据时因为 = 和分组边界,涨幅可能不止 33%。

  2. Base64 资源通常跟随 HTML/CSS/JS 一起缓存,不能像独立图片那样细粒度复用缓存。
  3. Base64 编码后的字符串无法直接阅读,且解码需要额外的计算开销(虽然不大)。
  4. 不是加密:可直接解码,不提供保密性。

应用场景:

  • 小体积资源内联:如 HTML/CSS 中嵌入小图标(data URL:< img src="data:image/png;base64,...">)。
  • 二进制数据需要放进“只适合文本”的通道:如邮件内容、JSON/XML 字段、日志、配置文件。
  • 令牌与签名相关文本:如 JWT 的各段(常见是 Base64URL 变体)。
  • URL/表单中传输少量二进制片段(更常见用 Base64URL,避免 + /)。