Character Set(字符集)

字符是可打印符号(字母、数字、标点符号)或非打印符号(例如,回车或软连字符)。字符集是一种编码系统,使计算机识别 Character(字符),包括字母、数字、标点符号和空格。

ASCII 字符集包括英语字母、符号;ISO-8859-6 字符集包括许多基于阿拉伯语言文字的字母、符号;Unicode 字符集涵盖世界上多数语言文字字符。

早期,由于使用不同的语言,各国开发了自己的字符集,例如日语的 Kanji JIS(例如 Shift-JIS、EUC-JP 等),繁体中文的 Big5 和俄语的 KOI8-R。Unicode 因其对通用语言支持而逐渐成为最被接受的字符集。

如果字符集使用不正确(例如,使用 Big5 编码的文章的 Unicode),可能会看到损坏的字符,这些字符称为乱码。

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)

ASCII 是 Internet 上计算机之间使用的第一个字符集(编码标准)。

  • 是计算机和电子设备的标准字符集。
  • 为每个可存储字符定义了一个唯一的二进制数字。
  • 包含从 0-9 的数字,从 A-Z 的大写和小写英文字母以及一些特殊字符(比如 ! $ + - ( ) @ < > ,)的 128 个字符的 7 位字符集。
  • 现代计算机和 HTML 中,以及在 Internet 中,使用的字符集均基于 ASCII。
  • ISO-8859-1(HTML 4.01 中默认)和 UTF-8(HTML5 中默认)都是基于 ASCII 构建的。

ASCII 的最大缺点是,它排除了非英文字母。如今,ASCII 仍在使用,特别是在大型主机计算机系统中。

ISO-8859-1(International Standards Organization,国际标准组织)

ISO-8859-1 是 HTML 4.01 中的默认字符。ISO-8859-1 是 ASCII 的扩展,增加了国际字符。

<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1">
  • ISO-8859-1 的第一部分(实体编号为 0-127)是原始 ASCII 字符集。
  • 在 ISO-8859-1 中,未定义 128 到 159 之间的字符。大多数浏览器将显示 Windows-1252 字符集中的这些字符。
  • ISO-8859-1 从 160 到 191 的部分包含常用的特殊字符。
  • ISO-8859-1 从 192 到 255(215(×)、247(÷)除外)的部分包含西欧国家/地区使用的字符。

由于 ISO-8859 中的字符集大小受限制,并且不兼容多语言环境,因此 Unicode 联盟开发了 Unicode 标准字符集,并与标准开发组织(例如 ISO、W3C 和 ECMA)开展合作。

Unicode(统一码)

Unicode 标准(几乎)涵盖了世界上所有的字符、标点符号和符号。支持 HTML、XML、Java、JavaScript、电子邮件、ASP、PHP 等独立于平台和语言的文本处理、存储和传输。它的目标是用其标准的 Unicode 转换格式(UTF)替换现有的字符集。大多数操作系统和所有现代浏览器中都支持 Unicode 标准。

Unicode 和 UTF-8 之间的区别

  1. Unicode 是字符集。UTF-8 是编码。
  2. Unicode 只规定了每个字符的码点,从 0 开始,为每个符号指定一个唯一的十进制数字编号,叫做码点(code point)。例如,A = 65,B = 66,C = 67,...,"hello":104 101 108 108 111。用什么样的字节序表示这个码点则需要字符编码实现,UTF-8 编码将这些数字转换为二进制数字以存储在计算机中,"hello" 将被转换为:01101000 01100101 01101100 01101100 01101111。
  3. 字符集将字符转换为数字。字符编码将数字转换为二进制。

Character encoding(字符编码)

字符编码定义了字节和文本之间的映射。字节序列有不同的文本解释。通过指定特定的编码(例如 UTF-8),来指定如何解释字节序列。例如在 HTML 中,我们通常使用以下行声明 UTF-8 的字符编码:

<meta charset="utf-8">

UTF-32

bg2014121116.png

每个码点使用四个字节表示,字节内容与码点一一对应。比如,码点 0 用四个字节的 0 表示,码点 597D 就在前面加两个字节的 0。

U+0000 = 0x0000 0000
U+597D = 0x0000 597D

UTF-32 的优点在于,转换规则简单直观,查找效率高。缺点在于浪费空间,同样内容的英语文本,它会比 ASCII 编码大四倍。这个缺点很致命,导致实际上没有人使用这种编码方法,HTML 5 标准就明文规定,网页不得编码成 UTF-32。

UTF-8

UTF-8 (UCS Transformation Format 8) 是一种变长的编码方法,字符长度从 1 个字节到 4 个字节不等,节省了不必要的空间。

  • UTF-8 向后兼容 ASCII 字符集,前 128 个 UTF-8 字符,只使用 1 个字节表示,与前 128 个 ASCII 字符 (编号为 0-127) 完全相同,这意味着现有的 ASCII 文本已经是有效的 UTF-8。
  • 所有其他字符都使用两到四个字节。每个字节都有一些用于编码目的的保留位。
  • 由于非 ASCII 字符需要一个以上的字节来存储,如果字节被分开而不重新组合,它们就有被损坏的风险。
  • 与其他 Unicode 编码相比,特别是 UTF-16,在 UTF-8 中 ASCII 字符占用的空间只有一半,可是在一些字符的 UTF-8 编码占用的空间就要多出 1/3,特别是中文、日文和韩文(CJK)这样的方块文字。
  • 与 UTF-16 或其他 Unicode 编码相比,对于不支持 Unicode 和 XML 的系统,UTF-8 更不容易造成问题。
  • HTML5 中的默认字符编码为 UTF-8。

UTF-16

bg2014121106.png

介于 UTF-32 与 UTF-8 之间,同时结合了定长和变长两种编码方法的特点。

它的编码规则很简单:基本平面的字符占用 2 个字节,辅助平面的字符占用 4 个字节。也就是说,UTF-16 的编码长度要么是 2 个字节(U+0000 到 U+FFFF),要么是 4 个字节(U+010000 到 U+10FFFF)。

UTF-16 比起 UTF-8,好处在于大部分字符都以固定长度的字节(2 字节)存储,但 UTF-16 却无法兼容于 ASCII 编码。

ES6 对 Unicode 的增强

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

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

但是,这种表示法对4字节的码点无效。ES6 修正了这个问题,只要将码点放在大括号内,就能正确识别。

'𝌆' === '\u1D306' // false
'𝌆' === '\u{1D306}' // true

为了得到字符串的正确长度,可以用下面的方式:

Array.from('\u{1D306}').length // 1

字符串处理函数

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

  • String.fromCodePoint():从 Unicode 码点返回对应字符,代替 String.fromCharCode。
  • String.prototype.codePointAt():从字符返回对应的码点,代替 String.prototype.charCodeAt。
String.fromCodePoint(127752) // 🌈
String.fromCharCode(127752) //  大于 0xFFFF/65535 字符不能转换

正则表达式

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

/^.$/.test('𝌆') // false
/^.$/u.test('𝌆') // true

Unicode 正规化

有些字符除了字母以外,还有附加符号。比如,汉语拼音的 Ǒ,字母上面的声调就是附加符号。对于许多欧洲语言来说,声调符号是非常重要的。

Unicode 提供了两种表示方法。一种是带附加符号的单个字符,即一个码点表示一个字符,比如 Ǒ 的码点是 U+01D1;另一种是将附加符号单独作为一个码点,与主体字符复合显示,即两个码点表示一个字符,比如 Ǒ 可以写成 O(U+004F) + ˇ(U+030C)。

// 方法一
'\u01D1' // 'Ǒ'

// 方法二
'\u004F\u030C' // 'Ǒ'

这两种表示方法,视觉和语义都完全一样,理应作为等同情况处理。但是,JavaScript 无法辨别。

'\u01D1'==='\u004F\u030C' // false

ES6 提供了 normalize 方法,允许 "Unicode 正规化",即将两种方法转为同样的序列。

'\u01D1'.normalize() === '\u004F\u030C'.normalize() // true