最近在看 NLP 里用的 BBPE (Byte level Byte-pair Encoding) 分词算法,顺便查了下字符的编码方式,做个笔记。
字母(包括大小写),数字还有一些常用的符号(例如*、#、@等)以及汉字,在计算机中存储时要使用二进制数来表示,而具体用哪些二进制数字表示哪个符号,这就是编码。如果不同的计算机要想互相通信而不造成混乱,那么每台计算机就必须使用相同的编码规则。
ASCII
美国有关的标准化组织于 1963年推出了 ASCII 编码,一共包含 127 个字符,其中 95 个是可显示字符,只能用于显示英语。
每个字符对应一个 0-127 之间的数,叫做码点;而各个字符与码点的对应,称为码表。如何在计算机中存储这些码点呢?一个直接的做法就是用二进制数表示这些数字。由于一共有 128 个字符,8 个比特足以表示。因此 ASCII 编码下,每个字符占一个字节(8 bits),可以用两个16进制数表示。
Unicode
但英语字符还是太少了。其他国家的语言,像汉语、日语、阿拉伯语,它们都有独特的字符。Unicode 就是为了探索如何对这些字符进行编码。
Unicode,译为万国码,是一种通用字符集,编码了世界上大部分的文字系统(还包括 emoji 噢),使得电脑能以通用划一的字符集来处理和显示文字。
Unicode 的实现方式不同于编码方式。一个字符的 Unicode 编码确定,即它对应的码点是确定的。比如 💩 这个 emoji 对应的码点是十进制数字 128169。另外,Unicode 是适配 ASCII 编码的:ASCII 中的字符对应的码点与 Unicode 中一致。
Python 中的
ord
函数可以查询字符对应的 Unicode 码点。
现在已经把字符和数字一一对应了。如何在计算机中存储这个数?这里就有不同的实现方式了:Unicode的实现方式称为 Unicode 转换格式(Unicode Transformation Format,简称为UTF)。
UTF-32
UTF-32 简单粗暴,直接用 32 bits(4 个字节)表示所有的字符。这样让英语使用者很头疼:本来用 ASCII 编码,每个字符只占一个字节;但如果用 UTF-32 方式存储,同样的内容却要占用 4 倍空间,其中大部分是无意义的占位符。
UTF-8
基于此,UTF-8 来了。这是一种变长编码的实现方式,用 1-4 个字节表示一个字符:对常见的英文字符用更少的字节数表示,对于生僻字符用更多字节数表示。
UTF-8编码的字节分布如下:
- 1字节:用于U+0000到U+007F的字符,即ASCII字符。
- 2字节:用于U+0080到U+07FF的字符,这包括了拉丁字母、希腊字母、希伯来字母等。
- 3字节:用于U+0800到U+FFFF的字符,这包括了大部分常用中文字符、日文字符、韩文字符等。
- 4字节:用于U+10000到U+10FFFF的字符,这包括了其他一些不常用的字符和一些辅助字符。
比如“你”这个汉字用三个字节表示,在十六进制下为 “0xE4 0xBD 0xA0”.
这样一来,皆大欢喜。既拓展了字符集,又优化了存储空间。现在 UTF-8 已经成为最常用的字符编码方式了,也是大部分软件默认的编码方式。但打开或存储文件的时候,最好还是瞄一眼是不是真的用了 UTF-8。否则可能会有“锟斤拷”,“烫烫烫”等着你噢 🤣