编码 & 字符集 & javascript

字符集

  • 计算机只能存储二进制数据,为了存储字母、汉字、emoji 等,需要将其转换成二进制数据,这些转换就是根据字符集来做的。

  • ASCII 码就是一种字符集:

    ASCII 码一共规定了 128 个字符的编码,比如空格 SPACE 是 32(二进制 00100000),大写的字母 A 是 65(二进制 01000001)。这 128 个符号(包括 32 个不能打印出来的控制符号),只占用了一个字节的后面 7 位,最前面的一位统一规定为 0。

  • Unicode 是一种更大更全的字符集:U+0041 表示英语的大写字母 A,U+4E25 表示汉字严,可以查询 unicode.org,或者专门的汉字对应表

字符转换成二进制后,还需要解决两个问题

  1. 第一个问题是,如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?

  2. 第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是 0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

编码(解决上述两个问题)

  • UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示)

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

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

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

  • UTF-8 示例

    字符Næ

    Unicode 编号(二进制)

    01001110

    11100110

    00101110 11101100

    Unicode 编号(十六进制)

    4E

    E6

    2E EC

    UTF-8 编码(二进制)

    01001110

    11000011 10100110

    11100010 10111011 10101100

    UTF-8 编码(十六进制)

    4E

    C3 A6

    E2 BB AC

  • UFT-16 使用 2 个或者 4 个字节来存储。 对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储,并且直接存储 Unicode 编号,不用进行编码转换,这跟 UTF-32 非常类似。

javascript

  • 1 字节是 8 比特,表示成 2 进制需要 8 位,表示成 16 进制需要 2 位

  • js 中用 UTF-16 编码,一个字符编码成 1 个或 2 个 代码单元。每个代码单元占 2 个字节,需要 4 位 16 进制表示。

  • 可以在 js 字符串里使用 unicode 如 '\u00E9'

  • js 中字符串的 length 属性是指代码单元的数量,不是指字符的数量

  • iterator 可以获取部分包含两个代码单元的字符串的正常长度(字符数而不是代码单元数),但不能正确获取包含 combining marks 的字符数,如"é",可以使用 normalize 方法转换后获取字符数

  • 一个字符可以用不同的代码点组合,如"é"

  • 对于 js 的===,即使打印出来的字符串一样,结果也可能是 false,需要使用 normalize 方法

;[...str].length // 返回字符数量,而不是代码单元数量,对于combining marks无效

'\u00E9' === 'é' // true
'\u0065\u0301' === 'é' // true
'\u00E9' === '\u0065\u0301' // false
'é' === 'é' // false
'\u00E9'.normalize() === '\u0065\u0301'.normalize() // true
'é'.normalize() === 'é'.normalize() // true
const s1 = '\u00E9' //é
const s3 = 'e\u0301' //é
s1 === s3 // false
s1.normalize() === s3.normalize() // true
'é'.normalize().length // 最准确
;[...'é'].length
for (let codePoint of '\ud83d\udc0e\ud83d\udc71\u2764') {
  console.log(codePoint.codePointAt(0).toString(16))
}

参考

最后更新于