💯💯💯 原来 Base64 如此简单!!!

10,930 阅读4分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

Intro

作为前端开发者,我们在很多场景中会使用到 Base64,但很惭愧,笔者对其原理并不熟悉,导致在遇到实际问题时耗费了很多时间去 📌 定位问题。这篇文章,将对 Base64 进行刨根问底,一探究竟,下次就不再陌生了。

这是又一篇和编码知识相关的内容,关联之前写过的几篇相关文章:

前端场景 🥷

在笔者过往的工作经历中,大概有以下几种场景会用到 Base64

  • 将小图处理成 Base64,以 Data URLs 的形式传入 img 标签的 srcbackground-image,这样可以减少 HTTP 请求次数;
  • 通过 Canvas 实现图片压缩/裁切/绘制,最终调用 canvas.toDataURL() 方法,将图片传给客户端或后端进行保存;
  • 对二进制文件做 Hash 算法 (例如 SHA-1,SHA256)生成 checksum,最后使用 Base64 encode 使得摘要可打印,方便判断是否一致;
  • ...

如果仔细看,其实这几种使用场景做的事情都是:将二进制 bytes 通过新的编码系统编码成可读文本,方便以文本形式表示、传输、存储内容

那做 Base64 的必要性是没什么,直接输出二进制不行吗?答案当然是不行了,例如👆的第一个例子,HTML 是以文本形式传输的,而图片是二进制文件(一堆 0 和 1),强行将图片按 utf-8 编码输出,保不齐会产生 IMG 结束标签(例如 "/>),导致 HTML 文本解析异常。

在文本协议中传输二进制终归是需要编码的,Base64 就是其中一种比较常见通用的编码方案。下面继续看它的编码逻辑。

编码逻辑 ❓

首先,Base64 有自己一套字符集,包括 A-Z, a-z, 0-9, +, /=,总共 65 个字符。除了作为填充字符的 = 之外,每个字符都有自己的一个 Index。(注意和 ASCII 的 charCode 不是一回事)

image.png

这些字符全都是常见的可打印字符,Base64 将使用这 65 个字符来表示任意二进制内容。它的规则是以 6 个 bit 为一个单元,每 4 个单元为一组。

可是一个字节最小都有 8 个 bit,只用 6 个 bit 是如何表示的呢?直接看 wiki百科的实例,对字符串 Man 做 base64encode。

image.png

每个字符占用 8-bit,三个字符总共 24-bit,刚好划分为 6-bit * 4 ,按每 6-bit 为一个单元分别编码得到 Index,再根据字典表映射到 base64 中的字母。就得到 Man 做 base64 之后的结果 TWFu

那如果不是刚好 24-bit 呢?比如只有两个字符 Ma 是怎么编码的?

image.png

很简单,如果 6-bit 不是完整的,用 0 补位(例如第三个单元只有 0001,末尾补充两个 0);如果 6-bit 是空的用填充字符 = 占位(例如第四个单元)。所以 Ma 经过 base64 编码后是 TWE=

A= 表示的含义不一样,A 是 6-bit 全为 0,= 是这个单元是填充补齐的。

这就是 Base64 的编码逻辑,根据这个规则,我们可以得出一些结论:

  1. 编码后内容占用体积会增加,至少是原来的 4/3 倍
  2. 编码后内容都满足 6-bit 为一个单元,每 4 个单元为一组的这个规律(Base64 的 64 就是这么来的)
  3. 编码后文本至多仅包含 2= 号,且仅会在结尾出现。

如何使用 ✂️

Node

Node 中的 buffer 模块是专门用来弥补 JS 对二进制操作的空缺的,其原生支持了 base64 的的编码解码。

Buffer.from('Man').toString('base64')
// TWFu
Buffer.from('TWFu', 'base64').toString()
// Man

浏览器

浏览器环境中也提供了 base64 编码解码的方法,名字叫 atob (ASCII to Binary),btoa(Binary to ASCII)。

btoa('Man')
// TWFu
atob('TWFu')
// Man

遗憾的是这俩方法仅支持 (U+0080) - FF (U+00FF) 这个范围内的字符编/解码,如果不在这个范围内,就会报错。

Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

要满足日常工作中对众多 Unicode 字符编解码的需求,可以使用 webtoolkit 提供的方法。

Linux

linux 中也内置了 base64 命令,可以使用管道符号 | 将需要编码解码的内容传递给它。

$ echo -n "Man" | base64
TWFu

$ echo -n "TWFu" | base64 -d
Man

Ending

可以看到 Base64 无论是应用还是其自身的编码逻辑都非常简单易懂,看完这篇文章希望你也对 Base64 不再陌生(记住 6-bit,4 个为一组就可以🌶🌶🌶)

参考文档