UTF-8编码

Unicode

需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储

比如,汉字”严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF8

UTF8编码使用1到4个字节来表示每个Unicode码点, ASCII部分字符只使用1个字节, 常用字符部分使用2或3个字节表示。 每个符号编码后第一个字节的高端bit位用于表示总共有多少编码个字节。 如果第一个字节的高端bit为0, 则表示对应7bit的ASCII字符, ASCII字符每个字符依然是一个字节, 和传统的ASCII编码兼容。 如果第一个字节的高端bit是110, 则说明需要2个字节;后续的每个高端bit都以10开头。

1
2
3
4
0xxxxxxx runes 0-127 (ASCII)
110xxxxx 10xxxxxx 128-2047 (values <128 unused)
1110xxxx 10xxxxxx 10xxxxxx 2048-65535 (values <2048 unused)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused)

变长的编码无法直接通过索引来访问第n个字符, 但是UTF8编码获得了很多额外的优点。 首先UTF8编码比较紧凑, 完全兼容ASCII码, 并且可以自动同步:它可以通过向前回朔最多2个字节就能确定当前字符编码的开始字节的位置。 它也是一个前缀编码, 所以当从左向右解码时不会有任何歧义也并不需要向前查看

没有任何字符的编码是其它字符编码的子串, 或是其它编码序列的字串, 因此搜索一个字符时只要搜索它的字节编码序列即可, 不用担心前后的上下文会对搜索结果产生干扰。 同时UTF8编码的顺序和Unicode码点的顺序一致, 因此可以直接排序UTF8编码序列。 同时因为没有嵌入的NUL(0)字节, 可以很好地兼容那些使用NUL作为字符串结尾的编程语言。

Unicode转义字符让我们可以通过Unicode码点输入特殊的字符。 有两种形式:\uhhhh对应16bit的码点值, \Uhhhhhhhh对应32bit的码点值, 其中h是一个十六进制数字;一般很少需要使用32bit的形式。 每一个对应码点的UTF8编码。 例如:下面的字母串面值都表示相同的值:

1
2
3
4
"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"
"\U00004e16\U0000754c"

上面三个转义序列都为第一个字符串提供替代写法, 但是它们的值都是相同的。

Unicode转义也可以使用在rune字符中。 下面三个字符是等价的:

1
'世' '\u4e16' '\U00004e16'

对于小于256码点值可以写在一个十六进制转义字节中, 例如\x41对应字符A, 但是对于更大的码点则必须使用\u\U转义形式。 因此, \xe4\xb8\x96并不是一个合法的rune字符, 虽然这三个字节对应一个有效的UTF8编码的码点。

得益于UTF8编码优良的设计, 诸多字符串操作都不需要解码操作。 我们可以不用解码直接测试一个字符串是否是另一个字符串的前缀,或者是后缀测试,或者是包含子串测试.

列子

打开”记事本”程序Notepad.exe,新建一个文本文件,内容就是一个”严”字,依次采用ANSI,Unicode,Unicode big endian 和 UTF-8编码方式保存。
然后,用文本编辑软件UltraEdit中的”十六进制功能”,观察该文件的内部编码方式。

  • ANSI:文件的编码就是两个字节”D1 CF”,这正是”严”的GB2312编码,这也暗示GB2312是采用大头方式存储的。
  • Unicode:编码是四个字节”FF FE 25 4E”,其中”FF FE”表明是小头方式存储,真正的编码是4E25。
  • Unicode big endian:编码是四个字节”FE FF 4E 25″,其中”FE FF”表明是大头方式存储。
  • UTF-8:编码是六个字节”EF BB BF E4 B8 A5″,前三个字节”EF BB BF”表示这是UTF-8编码,后三个”E4B8A5″就是”严”的具体编码,它的存储顺序与编码顺序是一致的。

注意,windows下通过记事本保存的utf8默认是带BOM的utf,而一般建议都是使用不带BOM的utf8编码

重点

在内存中,为了保存所有的字符,肯定是要使用unicode的,但是使用的是哪种实现方式呢?实际上,没有选用utf8,因为utf8编码虽然对于查找字符很快速,但是由于每个字符所占字节大小不一样,要想识别的话必须从开始识别,而为了效率,内存中会使用每个字符都占4个字节的utf32进行存储

参考文档

坚持原创技术分享,您的支持将鼓励我继续创作!