Unicode和UTF-x

字符集是字符的集合,常见的有:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等

字符编码则是编码规则,是计算机用于解析字符的规则。常见的有ISO-8859-1,GB2312,GBK,UTF-8,UTF-16等。

本文讲讲 Unicode 字符集和它的常见编码格式。

Unicode

Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。

Unicode的编码空间可以划分为17个平面(plane),每个平面包含65,536个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0),其他平面称为辅助平面(Supplementary Planes)。

在基本多语言平面内,从U+D800到U+DFFF之间的码位区段是空出来的码位。UTF-16利用这块码位来对辅助平面的字符进行编码。

UTF-8

UTF-8是一种可变长度字符编码。

现在看这样一句话:It's 知乎日报,在Unicode中对应的码和二进制分别是:

I 0049 00000000 01001001
t 0074 00000000 01110100
' 0027 00000000 00100111
s 0073 00000000 01110011
  0020 00000000 00100000
知 77e5 01110111 11100101
乎 4e4e 01001110 01001110
日 65e5 01100101 11100101
报 62a5 01100010 10100101

可以看到,英文的字符前9位都是0,太浪费了,因为utf-8采用了可变长度编码,以8bits为最小变长。编码后如下:

I 01001001
t 01110100
' 00100111
s 01110011
  00100000
知 11100111 10011111 10100101
乎 11100100 10111001 10001110
日 11100110 10010111 10100101
报 11100110 10001010 10100101

它的编码规则是这样的:

0000〜007F:0XXXXXXX
0080〜07FF:110XXXXX 10XXXXXX
0800〜FFFF:1110XXXX 10XXXXXX 10XXXXXX
10000〜10FFFF:11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

可以看到utf8是一种前缀编码,编码成一个字节的首位为0,编码成两个字节的,第一个字节的前两位为1,编码为三个字节的,第一个字节前三为为1...就是通过这种手段,计算机知道接下来的一个字符包含多少个字节。

自2009年以来,UTF-8一直是万维网的最主要的编码形式。截止到2019年11月, 在所有网页中,UTF-8编码应用率高达94.3%。第二热门的多字节编码方式Shift JIS和GB 2312分别具有0.3%和0.2%的占有率。几乎所有的互联网联盟都建议采用UTF-8编码。

结构:

UTF-8原本使用1-6个字节为每个字符编码,但2003年11月UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF,也就是说最多四个字节。

  • 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
  • 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要两个字节编码(Unicode范围由U+0080至U+07FF)。
  • 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字,如大部分的汉字)使用三个字节编码(Unicode范围由U+0800至U+FFFF)。
  • 其他极少使用的Unicode 辅助平面的字符使用四字节编码。

特点

Ascll码是它的子集,所有英文只需要一个字节,对于欧美地区而言非常的高效且便捷。但中文字符基本都需要三个字节,比较不方便。

UTF-16

utf-16也是一种长度的编码,每个字符占用两个字节或者四个字节。

  • 对于基本平面字符,unicode码和utf-16相同,只占用两个字节。

  • 对于辅助平面字符,被编码成一对码元,占用四个字节:

    • 码位减去 0x10000, 则得到 20 bit 长的 0 - 0xFFFFF。
    • 高位的10bit,加上 0xD800 得到第一个码元,又称高位或前导,值的范围是 0xD800...0xDBFF
    • 低位的10bit,加上 0xDC00 得到第二个码元,又称低位或后尾,值的范围是 0xDC00...0xDFFF

    这样设计,那么对于一个两个字节,很容易判断其是基本字符,还是辅助字符,而且很好判断是前导还是后尾。这样每次读取都是连续的 2 字节。

不同的操作系统对字节顺序的理解不一致。如某字符为十六进制编码4E59,按两个字节拆分为4E和59,在Mac上读取时是从低字节开始,那么会认为此4E59编码为594E,找到的字符为“奎”,而在Windows上从高字节开始读取,则编码为U+4E59的字符为“乙”。此类情况说明UTF-16的编码顺序若不加以人为定义就可能发生混淆,于是在UTF-16编码实现方式中使用了大端序(Big-Endian,简写为UTF-16 BE)、小端序(Little-Endian,简写为UTF-16 LE)的概念,以确保不同的字节序也能呈现相同内容,目前Windows和Linux默认使用UTF-16 LE。

utf-8 和 utf-16 如何选择

utf-16的特点是编码规整,方便cpu读写。因此在需要进行内存读写时,采用utf-16

但是utf-16对于ascll码字符存在空间浪费,而多数时候ascll码字符更多,因此在网络或者存储时,普遍采用utf-8.

另外,对于cjk(中日韩文字)编码时,很多字符都是在于基本平面内,使用 utf-16 只需要两字节,而使用 utf-8 通常需要 3 字节。也就是说,在cjk文字偏多时,采用utf-16更加有空间优势。

  • Java的编码格式是什么?

    java的编码格式要分情况来看,在类文件中编码默认是 utf-8,可以手动指定;在 jvm 运行时,即在内存中,都转换为 utf-16。原因就是 utf-16 方便 cpu 读写,而utf-8对于ascll字符多的情况下更省内存。

UTF-32

utf-32是定长的编码,每个码位使用四个字节进行编码。优点是和unicode一一对应,缺点是太浪费空间。

参考:

https://zh.wikipedia.org/wiki/Unicode

https://zh.wikipedia.org/wiki/UTF-8

https://zh.wikipedia.org/wiki/UTF-16

https://www.jb51.net/article/81416.htm

https://www.zhihu.com/question/308677093

上一篇:Python中的常见标准异常


下一篇:使用命令行执行java程序时出现错误: 编码GBK的不可映射字符