BMP (基本多文种平面)

Unicode 中最初的 65,536 个码位 (U+0000〜U+FFFF) 所在的区域。日常使用的绝大多数字符都收录于此,超出该范围的字符在 UTF-16 中要用代理对来表示。

BMP (Basic Multilingual Plane,基本多文种平面) 是 Unicode 的 17 个"平面" (plane) 中的第一个平面 (Plane 0)。它包含从 U+0000 到 U+FFFF 的 65,536 个码位,收录了 ASCII、拉丁字母、希腊字母、西里尔字母、阿拉伯字母、平假名、片假名、CJK 统一汉字的基本集 (约 20,000 字) 等世界主要文字体系的绝大部分。

BMP 之外还有 16 个辅助平面 (Supplementary Planes)。Plane 1 (SMP,辅助多文种平面) 包含表情符号、古代文字、音乐符号等。Plane 2 (SIP,辅助表意文字平面) 收录了以前的字符编码标准中没有包含的汉字。截至 2026 年,CJK 统一汉字扩展 B、C、D、E、F、I 位于这个平面,而扩展 G、H、J 被放在 Plane 3 (TIP,第三表意文字平面)。扩展的符号顺序与平面的编号并不一致,因此如果认为"扩展 B 以后全都在 Plane 2"就会出错。这些辅助平面的字符拥有 U+10000 以上的码位。

BMP 与辅助平面的区别会在使用 UTF-16 编码的环境中影响字符计数。UTF-16 中,BMP 的字符可以用 2 字节 (1 个代码单元) 表示,而辅助平面的字符需要 4 字节 (2 个代码单元 = 代理对)。JavaScript 的 String.length 返回的是代码单元数,因此在包含表情符号 (辅助平面) 的字符串上,会返回比看上去的字符数更大的值。

来看具体的例子。"A" (U+0041,BMP 内) 的 length 为 1。"𠮷" (U+20BB7,CJK 统一汉字扩展 B) 是 BMP 之外的字符,length 为 2。"😀" (U+1F600,表情符号) 也在 BMP 之外,length 为 2。再者,"👨‍👩‍👧‍👦" (家庭表情符号) 由 7 个码位构成 (其中 4 个在 BMP 之外),length 为 11。其明细是:BMP 之外的 4 个字符各占 2 个代码单元,中间的 3 个 ZWJ 各占 1 个代码单元。

实务中 BMP 的边界成为问题的典型情形,是保存目标只能处理 BMP 内字符的场合。MySQL 的 utf8mb3 (以前的 utf8) 是每个字符最多 3 字节、只能保存 BMP 内字符的字符集,放入表情符号或扩展汉字会报错或丢失字符。要保存含表情符号的文本,就必须选择 utf8mb4。在把字符数上限定为 UTF-16 代码单元数的表单上,同样的边界也会露面,一个表情符号就消耗 2 个字符的份额。某个字符是否在 BMP 之外,可以用 codePointAt(0) > 0xFFFF 来判定。

要准确地计数字符,需要不区分 BMP 内外的方法。在 JavaScript 中,可以用 Array.from(str).length 或 [...str].length 取得码位单位的字符数。如果要更精确,使用 Intl.Segmenter 按书写素簇为单位计数,就能把组合字符和表情符号序列也正确地数作 1 个字符。

BMP 之中也有特殊的区域。U+D800〜U+DFFF 是为代理对保留的,单独并不表示有效的字符。U+E000〜U+F8FF 是私用区 (Private Use Area),供字体厂商或企业分配自定义字符。U+FDD0〜U+FDEF 以及各平面末尾的 2 个码位是"非字符" (noncharacter),永久不会被分配给字符。

分享这篇文章