八一八 unicode和字符编码的那些事


#1

曾经不止一次的看到, “UNICODE 字符要两个字节存储” 这样的说法。不仅仅出现在初学者的言论里,也出现在一些大师的书中。

其实这是完全错误的观点, 因为这句话混淆了 字符集和字符编码。

字符集

字符集说白了,就是一个 map 。 里面规定了一个 数字 到一个 字符 的映射关系。 ascii 就是这样一张表,表格列出了 127 个映射关系。 欧洲的国家通常使用一张有 256 个映射关系的表,把希腊字母给编进去了。 因为一些历史原因,欧洲不同的国家有不同的表,虽然他们都是 256 个映射关系。

当然,UNICODE也是这样一张表,而且是大的多的一张表,里面有近 20万个字符和数字之间的对应关系。很大很大的表。

字符编码

有了字符集后,如何表示那张表里的数字,就是字符编码的事情了。 对于 ASCII 来说,这张表只有 127 个, 于是使用一个字节进行编码,最高位总是0。虽然浪费了一个 bit 的编码,但是如果大量传输的时候,丢了一个 bit 就会导致后续字节最高位不总是 0 了,这样就能检测到一些传输出错的情况。

对于欧洲的那些编码来说,他们就是 256 个映射的,所以直接一个字节编码一个数字即可。为了和 ascii 兼容,他们的前 127 个字符就是 ascii 里 抄袭过来的。

乱码

问题来了,欧洲的编码,虽然前面 127 个字符同 ascii ,但是后面的 。。。。 可不一样,相互都不一样的编码。 于是欧洲各国之间的文档嘛 , 呵呵,总是要乱码。 相互不能正确查看。

应对这个问题的办法就是,唯一的规定一个数字表示的字符。同样的数字,在任何环境下,都表示同一个字符。 这就是 UNICODE 里 UNI 的含义。

UNICODE

于是,就有了最初的 UNICODE 。 同样,这个映射表格,前面 127 个保持 ascii 兼容。 然后后面的嘛,就看欧洲的那些小国那个比较牛逼了,谁牛, 就排前面,获得数字比较小的码位。

这样总共排了一千多个。包括了欧洲用的各种字符,当然也包括了米国人用的 ascii。

UTF 和 UCS

UNICODE 这不是一个数字表示一个字符了嘛,当然,数字比较大, 一个字节才能存到 255 , 显然不够。 于是用 unsigned short 来存, 这样就能把最初的 UNICODE 码表里的字符全表示了。

因为 unsigned short = uint16_t = 16bit 长度,所以这种表示办法就叫 UCS16。

当然,一些人觉得, 16bit 怎么可能够用,你们太天真了!坚持使用一个 unsigned int 来表示。 于是 32bit 长度啊,这种表示办法就叫 UCS32。

前面说的这种表示办法,只是说数据放到内存里的时候这样存。一旦数据要传输给别的电脑,就要涉及到一个很重要的问题 “大端/小端” 。

为了避免大端小端问题,同时又能在传输的过程中保持 ASCII 兼容, UNIX 之父发明了 UTF-8 编码。 根据 UNICODE 里表示字符的那个“数字” 的大小,使用变长编码。0 - 127 的数字,表示为1个字节, 128 - 1023 的数字,表示为 2 个字节, 1024 以上的,就表示为 3 个字节 。。。 更大的数字需要更多的字节表示。最长可以达 6 个字节。 总共可以表达到最大 2^31 这么大的数字。

好了,这就是 UTF-8 传输编码了。

那么, UTF16 是怎么回事呢?

是这样的,原来用 ucs16 的人(其实就是微软啦)发现,其实 2 字节根本不够用啊!(废话,当然不够用,光汉字就十几万了,你个二货)。 于是,就修改了一下,如果编码大于 6 万多的嘛,就是用 4 个字节编码 。。。。得了, ucs16 摇身一变,变长了。 于是学 UTF-8 给自己起名叫 UTF16了。 然后 ucs16 就被废除了,因为根本容纳不了 unicode 嘛!于是 wchar_t 在 windows 平台名不副实了 。。。。。

问题是,2个字节为基础,还是存在大端小端问题啊!!!! 微软你个白痴。 于是微软说,那咱就都用小端吧, 我自己这个就叫 UTF-16-LE。 LE 就是 little ending , 小端。

Linuxer 笑而不语。 Linux 下的程序一直都用 UTF-8 , 内部处理的时候,使用 ucs32 (wchar_t 是 4 个字节)。


#2

本主题已全局置顶,它将始终显示在它所属分类的顶部。可以由版主解除置顶或者点击清除置顶按钮。


#3