您的位置:首页 > 其它

字符编码问题,UNICODE\UTF-8\UTF-16\UTF-32\UCS\ANSI\GBK\GB2312等乱七八糟的名词

2013-11-26 10:24 375 查看
字符编码的问题让我困惑了好久的一段时间,其实简单的想,字符编码没有什么东西的,可是想真弄明白还是花去了我一点时间,前端时间写了一个简单的log程序,主要的工作就是支持系统运行时输出日志信息,同时允许定制不同级别的日志输出,刚开始的时候只是写入到文件中,可用过之后发现,只是写入到文件中,在进行系统调试的时候不是很方便,还要打开文件,浏览,再返回源代码,如果能够有一个界面展示岂不是更好,列表显示日志,可以选择显示的日志级别,实现日志输出位置的定位,类似下图中展示的一样:




感觉还是能方便一点的,而显示窗口的实现可以通过exe或者dll的方式,这就需要进行两个独立进程之间的通信,为了支持多个日志显示窗口,我要枚举现在打开的窗口比较窗口名,还要创建命名管道,比较管道名,不幸的是,log显示窗口系统用的是unicode编码,而在写日志主系统程序的时候用的却是多字节编码,在进行通信的时候总是出现这样那样的问题,同时为了使得log主系统可以在服务的主程序选用不同的字符编码方案的时候可以正常工作,费了点事情,因为当时对字符编码不了解,然后就去网上查了下,总结一下子,同时,为了使用方便,写了一个VAUTF8类实现UTF8编码方案,同时实现不同编码方案的转化。

    这篇文章里主要阐述下面几个问题

    1: 字符和字节的区别

    2: Big Endian和Little Endian

    3: ASCII

    4: Unicode的定义

    5: Unicode的编码方案

    6: Unicode的实现方式

    7: 一些名词解释

字符和字节的区别

字节(octet):是一个八位的存储单元,取值范围一定是0~255。

字符(character):为语言意义上的符号,范围不一定。例如在UCS-2中定义的字符范围为0~65535,一个字符占用两个字节。

Big Endian和Little Endian

上面提到了一个字符可能占用多个字节,那么这多个字节在计算机中如何存储呢?比如字符0xABCD,它的存储格式到底是 AB CD,还是 CD AB 呢?

实际上两者都有可能,并分别有不同的名字。如果存储为 AB CD,则称为Big Endian;如果存储为 CD AB,则称为Little Endian

Big Endian:高字节在前,低字节在后

Little Endian:低字节在前,高字节在后

X86系统都是Little Endian的,也就是低字节在前,高字节在后.也就是低地址存储低位字节,高地址存储高位字节.

ASCII

ASCII使用数字32到 127来表示所有的英文字母,比如空格是32,字母"A"是65等等。使用7个比特就可以存储所有这样字符。那个时代的大多数计算机使用8个比特来,所以你不但可以存储全部的ASCII,而且还有一个比特可以多出来用作其他。如果你想,你可以把它用作你不可告人的目的。32以下的码字是不可打印的,它们属于控制字符,像7表示响铃,12表示打印机换纸。

标准中,对于低128个码字大家都无异议,差不多就是ASCII了,但对于高128个码字,根据你所在地的不同,会有不同的处理方式。我们称这样相异的编码系统为码页(code pages)。举个例子,比如在以色列发布的DOS中使用的码页是862,而在希腊使用的是737。它们的低128个完全相同,但从128往上,就有了很大差别。MS-DOS的国际版有很多这样的码页,涵盖了从英语到冰岛语各种语言,甚至还有一些"多语言"码页。但是还得说,如果想让希伯来语和希腊语在同一台计算机上和平共处,基本上没有可能。除非你自己写程序,程序中的显示部分直接使用位图。因为希伯来语对高128个码字的解释与希腊语压根不同。

在亚洲,更疯狂的事情正在上演。因为亚洲的字母系统中要上千个字母,8个比特无论如何也是满足不了的。一般的解决方案就是使用DBCS- "双字节字符集",即有的字母使用一个字节来表示,有的使用两个字节。所以处理字符串时,指针移动到下一个字符比较容易,但移动到上一个字符就变得非常危险了。于是s++或s—不再被鼓励使用,相应的比如Windows下的AnsiNext和AnsiPrev被用来处理这种情况。

不少人依然坚信一个字节就是一个字符,一个字符就是8个比特。当然,如果你从来都没有试着把一个字符串从一台计算机移到另一台计算机,或者你不用说除英文以外的另一种语言,那么你的坚信不会出问题。但是互联网出现让字符串在计算机间移动变得非常普遍,于是所有的混乱都爆发了。非常幸运,Unicode适时而生。

Unicode的定义

Unicode是为整合全世界的所有语言文字而诞生的。任何文字在Unicode中都对应一个值,这个值称为代码点(code point)。代码点的值通常写成 U+ABCD 的格式。

一些人误以为Unicode只是简单的使用16比特的码字,也就是说每一个字符对应 16比特,总共可以表示65536个字符。这是完全不正确的。

在Unicode中,一个字母被映射到一个叫做码点(code point)的东西,这个码点可以看作一个纯粹的逻辑概念。至于码点(code point)如何在内存或磁盘中存储是另外的一个故事了。

码点(code point)的形式:U+0639

U+的意思就是"Unicode",后面跟的数字是十六进制的。

事实上Unicode可以定义的字符数并没有上限,而且现在已经超过65536了。显然,并不是任何Unicode字符都可以用2个字节来表示了。

例如:Hello

在Unicode中,对应的码点(code point)如下:

U+0048 U+0065 U+006C U+006C U+006F

仅仅是一堆码点而已,或者说数字。不过到现在为止,我们还没有说这些码点究竟是如何存储到内存或如何表示在email信息中的

Unicode最早的编码想法,就是把每一个码点(code point)都存储在两个字节中,这也就导致了大多数人的误解。于是Hello就变成了:

00 48 00 65 00 6C 00 6C 00 6F

这样对吗?如下如何?

48 00 65 00 6C 00 6C 00 6F 00

技术上说,我相信这样是可以的。事实上,早期的实现者们的确想把Unicode的码点(code point)按照大端或小端两种方式存储,这样至少已经有两种存储Unicode的方法了。于是人们就必须使用FE FF作为每一个Unicode字符串的开头,我们称这个为Unicode Byte Order Mark。如果你互换了你的高位与低位,就变成了FF FE,这样读取这个字符串的程序就知道后面字节也需要互换了。可惜,不是每一个Unicode字符串都有字节序标记。

现在,看起来好像问题已经解决了,可是这帮程序员仍在抱怨。"看看这些零!"他们会这样说,因为他们是美国人,他们只看不会码点不会超过U+00FF的英文字母。同时他们也是California的嬉皮士,他们想节省一点。如果他们是得克萨斯人,可能他们就不会介意两倍的字节数。但是这样California节俭的人却无法忍受字符串所占空间翻倍。而且现在大堆的文档使用的是ANSI和DBCS字符集,谁去转换它们?于是这帮人选择忽略Unicode,继续自己的路,这显然让事情变得更糟。

Unicode的编码方式

Unicode的编码方式与ISO 10646通用字符集(Universal Character Set,UCS)概念相对应,目前实际应用的Unicode版本对应于UCS-2,使用16的编码空间。也就是每个字符占用2个字节。这样理论上一共最多可以表示216即65536个字符。基本满足各种语言的使用。实际上目前版本的Unicode尚未填充满这16
位编码,保留了大量空间作为特殊使用或将来扩展。

上述16位Unicode字符构成基本多文种平面(Basic Multilingual Plane,简称BMP)。最新(但未实际广泛使用)的Unicode版本定义了16个辅助平面,两者合起来至少需要占据21位的编码空间,比3字节略少。但事实上辅助平面字符仍然占用4字节编码空间,与UCS-4保持一致。未来版本会扩充到ISO
10646-1实现级别3,即涵盖UCS-4的所有字符。UCS-4是一个更大的尚未填充完全的31位字符集,加上恒为0的首位,共需占据32位,即4字节。理论上最多能表示231个字符,完全可以涵盖一切语言所用的符号。

BMP字符的Unicode编码表示为U+hhhh,其中每个h 代表一个十六进制数位。与UCS-2编码完全相同。对应的4字节UCS-4编码后两个字节一致,前两个字节的所有位均为0。

Unicode的实现方式:
Unicode Translation Format(UTF)

Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Translation Format,简称为UTF)。

Unicode.org定义了百万个以上的字符,如果将所有的字符用统一的格式表示,需要的是4个字节。“a“的Unicode表示就会变成0x00000061,而“一“的Unicode值是0x00004E00。实际上,这就是UTF32,Linux操作系统上所使用的Unicode方案。而Windows平台下默认的Unicode编码方式为Little Endian的UTF-16。

UTF16

UTF-16由RFC2781规定,它使用两个字节来表示一个代码点。

不难猜到,UTF-16是完全对应于UCS-2的,即把UCS-2规定的代码点通过Big Endian或Little Endian方式直接保存下来。UTF-16包括三种:UTF-16,UTF-16BE(Big Endian),UTF-16LE(Little Endian)。

UTF-16BE和UTF-16LE不难理解,而UTF-16就需要通过在文件开头以名为BOM(Byte Order Mark)的字符来表明文件是Big Endian还是Little Endian。BOM为U+FEFF这个字符。

其实BOM是个小聪明的想法。由于UCS-2没有定义U+FFFE,因此只要出现 FF FE 或者 FE FF 这样的字节序列,就可以认为它是U+FEFF,并且可以判断出是Big Endian还是Little Endian。

举个例子。“ABC”这三个字符用各种方式编码后的结果如下:





Windows平台下默认的Unicode编码为Little Endian的UTF-16(即上述的 FF FE 41 00 42 00 43 00)。你可以打开记事本,写上ABC,然后保存,再用二进制编辑器看看它的编码结果。

UTF32

UTF-32用四个字节表示代码点,这样就可以完全表示UCS-4的所有代码点,而无需像UTF-16那样使用复杂的算法。与UTF-16类似,UTF-32也包括UTF-32、UTF-32BE、UTF-32LE三种编码,UTF-32也同样需要BOM字符。仅用'ABC'举例:





但是,仔细分析可以发现,其实绝大部分字符只使用2个字节就可以表示了。英文的Unicode范围是0x0000-0x007F,中文的Unicode范围是0x4E00-0x9F**,真正需要扩展到4个字节来表示的字符少之又少,所以有些系统直接使用2个字节来表示Unicode。比如Windows系统上,Unicode就是两个字节的。对于那些需要4个字节才能表示的字符,使用一种代理的手法来扩展(其实就是在低两个字节上做一个标记,表示这是一个代理,需要连接上随后的两个字节,才能组成一个字符)。这样的好处是大量的节约了存取空间,也提高了处理的速度。这种Unicode表示方法就是UTF16。一般在Windows平台上,提到Unicode,那就是指UTF16了。

UTF8

UTF-16和UTF-32的一个缺点就是它们固定使用两个或四个字节,这样在表示纯ASCII文件时会有很多00字节,造成浪费。而RFC3629定义的 UTF-8则解决了这个问题。UTF-8用1~4个字节来表示代码点。表示方式如下:





可见,ASCII字符(U+0000~U+007F)部分完全使用一个字节,避免了存储空间的浪费。而且UTF-8不再需要BOM字节。

另外,从上表中可以看出,单字节编码的第一字节为[00-7F],双字节编码的第一字节为[C2-DF],三字节编码的第一字节为[E0-EF]。这样只要看到第一个字节的范围就可以知道编码的字节数。这样也可以大大简化算法。

GB2312,GBK,GB18030

从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。

在MS的IDE中我们可以看到这样一个选项



 

这里的Unicode一般就是指的UTF16,双字节宽字符,也就是wchar_t

而多字节字符集就是不确定使用的字节数的那种情况了……

一般在编写应用程序的时候,应该使用unicode字符编码方案,而在写文件的时候应该使用多字节字符编码方案,比较节省空间。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: