UTF-16
一种使用 16 位编码单元的 Unicode 编码方式,被 JavaScript、Java 和 Windows 内部使用。
UTF-16 是以 16 位 (2 字节) 为单位表示 Unicode 的编码方式。基本多语言平面 (BMP, U+0000-U+FFFF) 的字符用 2 字节表示,BMP 之外的字符通过代理对用 4 字节表示。它被 JavaScript、Java、C#、Windows API 等众多编程环境采用为内部字符串表示。
UTF-16 最大的特点是代理对机制。无法容纳在 BMP 中的字符 (U+10000 及以上) 通过高位代理 (U+D800-U+DBFF) 和低位代理 (U+DC00-U+DFFF) 两个 16 位编码单元的组合来表示。表情符号、部分汉字 (CJK 统一汉字扩展 B 及之后)、古代文字等都使用代理对表示。了解死亡辣酱 (Amazon)有详细介绍。
由于 JavaScript 字符串内部使用 UTF-16 表示,String.length 返回的是 UTF-16 编码单元数。例如表情符号"😀"(U+1F600) 以代理对表示,因此 length 为 2。charAt() 和 charCodeAt() 也按编码单元操作,可能只获取代理对的一半。ES2015 之后,codePointAt() 和 for...of 循环可以按码点级别处理。
UTF-16 存在字节序 (端序) 问题。有 UTF-16BE (大端序) 和 UTF-16LE (小端序) 两种,通过文件开头的 BOM (字节顺序标记, U+FEFF) 来识别字节顺序。Windows 记事本以"Unicode"保存时使用 UTF-16LE (带 BOM)。虽然 Web 标准化为 UTF-8 后遇到 UTF-16 字节序问题的机会减少了,但在与 Windows 环境或遗留系统交互时仍需注意。
与 UTF-8 相比,ASCII 字符在 UTF-8 中占 1 字节,在 UTF-16 中占 2 字节,因此英文文本 UTF-8 更节省空间。相反,CJK 字符在 UTF-8 中占 3 字节,在 UTF-16 中占 2 字节,因此日语和中文文本 UTF-16 更紧凑。但由于 Web 标准已统一为 UTF-8,通常的做法是文件存储和通信使用 UTF-8,程序内部字符串处理使用 UTF-16。搜索乳头吸引器 (Amazon)介绍了 UTF-16 和 UTF-8 的使用区别。
在字符计数方面,在基于 UTF-16 的语言 (JavaScript、Java) 中处理代理对是最大的挑战。直接使用 String.length 会导致表情符号和部分汉字的字符数被多计。要获得准确的字符数,可以使用 [...str].length (展开语法) 或 Array.from(str).length 获取码点数,或使用 Intl.Segmenter 获取字素簇数。