字符编码

字节与位

每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从00000000到11111111。

编码类型

ASCII

  • ASCII (American Standard Code for Information Interchange)

unicode

unicode.org

BMP

BMP(Basic Multilingual Plane)字符 Unicode 是目前绝大多数程序使用的字符编码。Unicode 标识符通过一个明确的名字和一个整数来作为它的码点/码位 (code point)。比如,“©️” 字符可以用“版权标志” 和码位 U+00A9 (0xA9,也可以写作十进制 169) 来表示。

码点/码位是为每一个字符提供一个全局唯一的标识符,一个码位映射一个字符,码位值的范围是从U+0000到U+10FFFF,可以表示超过110万个符号。

Unicode 字符分为 17 组平面,每个平面拥有 2^16 (65,536) 个码位。每一个码位都可以用 16 进制 xy0000 到 xyFFFF 来表示,这里的 xy 是表示一个 16 进制的值,从 00 到 10。

而当 xy 是 00 (码点范围是从U+0000到U+FFFF) 的时候,也就是 Unicode 最前 2^16 (65,536) 个字符,被称为基本平面 BMP(Basic Multilingual Plane),最常见的字符都在这个平面上,这也是 Unicode 最先定义和最先公布的一个平面。

其余 16 个平面(U+010000 到 U+10FFFF)称为补充平面(supplementary planes, or astral planes),也称之为补充字符,相对于 BMP 字符而言,这些字符称之为非 BMP 字符。要区分是非 BMP 字符很简单:其码位需要超过 4 位 16 进制表示

非BMP码点

要用两个字节的码点表示非BMP码点,需要用到代理对

前两个字节称为高位代理或者顶部代理,范围在 0xD800 到 0xDBFF 之间;

后两个字节称为低位代理或者尾部代理,范围在 0xDC00 到 0xDFFF 之间

UTF-8

UTF-8 的编码规则很简单,只有二条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

GBK

  • GB2312

UCS2

  • UCS (Universal Coded Character Set)

    Javascript 使用ucs-2编码

ES6 对unicode的支持

1. 正确识别字符

ES6可以自动识别4字节的码点

2. 码点表示法

JavaScript允许直接用码点表示Unicode字符,写法是"反斜杠+u+码点"。

'好' === '\u597D' // true

// ES6支持 使用{}符号来显示 4字节码点
'𝌆' === '\u{1D306}'    // true
'𝌆' === '\u1D306'      // false

3. 字符串处理函数

ES6新增了几个专门处理4字节码点的函数。

  • String.fromCodePoint():从Unicode码点返回对应字符

  • String.prototype.codePointAt():从字符返回对应的码点

codePointAt vs charCodeAt

对于 BMP 字符,charCodeAt 和 codePointAt 的行为一致,都是返回整个字符的码位 (code point);

对于非 BMP 字符,charCodeAt 通过指定参数,返回不同位置上的 code unit 值;而 codePointAt 当参数为 0 时,返回整个字符的码位 (code point),当参数为 1 时,返回的是低位代理 code unit 的值。

反转包含多字节码点的字符串

emoji字符一般是多字节码点。因为js不能识别两个字节以上的码点,因此使用以下方式反转字符串,会错误的截取字符:

'📦123'.split('').reverse().join(''); // '321��'

这里📦的unicode编码是四字节,split方法无法正确识别,将一个四字节的字符识别为两个两字节的字符,所以出现了乱码。

可以使用Array.from方法:

Array.from('📦123').reverse().join(''); // '321📦'

4. 正则表达式

ES6提供了u修饰符,对正则表达式添加4字节码点的支持。

/^.$/.text('𝌆');   // flase
/^.$/u.text('𝌆');  // true

5. 多字节码点字符的长度计算

多字节码点在使用高低位的代理对兼容两字节码点,可以通过正则表达式匹配代理对,匹配出多字节码点字符的长度。

var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; // 匹配UTF-16的代理对

function countSymbols(string) {
    return string
        // 把代理对改为一个BMP的字符.
        .replace(regexAstralSymbols, '_')
        // 这时候取长度就妥妥的啦.
        .length;
}
countSymbols('💩'); // 1

参考资料

Last updated