字符编码ASCII,Unicode和UTF-8

http://blog.csdn.net/pipisorry/article/details/42387045

字符编码介绍

文本,他们通常指显示在屏幕上的字符或者其他的记号;但是计算机不能直接处理这些字符和标记;它们只认识位(bit)和字节(byte)。实际上,从屏幕上的每一块文本都是以某种字符编码(character encoding)的方式保存的。粗略地说就是,字符编码提供一种映射,使屏幕上显示的内容和内存、磁盘内存储的内容对应起来。有许多种不同的字符编码,有一些是为特定的语言,比如俄语、中文或者英语,设计、优化的,另外一些则可以用于多种语言的编码。

在实际操作中则会比上边描述的更复杂一些。许多字符在几种编码里是共用的,但是在实际的内存或者磁盘上,不同的编码方式可能会使用不同的字节序列来存储他们。所以,你可以把字符编码当做一种解码密钥。当有人给你一个字节序列 -- 文件,网页,或者别的什么 -- 并且告诉你它们是文本时,就需要知道他们使用了何种编码方式,然后才能将这些字节序列解码成字符。如果他们给的是错误的密钥或者根本没有给你密钥,那就得自己来破解这段编码,这可是一个艰难的任务。有可能你使用了错误的解码方式,然后出现一些莫名其妙的结果。

你肯定见过这样的网页,在撇号(")该出现的地方被奇怪的像问号的字符替代了。这种情况通常意味着页面的作者没有正确的声明其使用的编码方式,浏览器只能自己来猜测,结果就是一些正确的和意料之外的字符的混合体。如果原文是英语,那只是不方便阅读而已;在其他的语言环境下,结果可能是完全不可读的。

现有的字符编码各类给世界上每种主要的语言都提供了编码方案。由于每种语言的各不相同,而且在以前内存和硬盘都很昂贵,所以每种字符编码都为特定的语言做了优化。上边这句话的意思是,每种编码都使用数字(0–255)来代表这种语言的字符。比如,你也许熟悉ASCII编码,它将英语中的字符都当做从0–127的数字来存储。(65表示大写的A,97表示小写的a,&c。)英语的字母表很简单,所以它能用不到128个数字表达出来。如果你懂得2进制计数的话,它只使用了一个字节内的7位。

西欧的一些语言,比如法语,西班牙语和德语等,比英语有更多的字母。或者,更准确的说,这些语言含有与变音符号(diacritical marks)组合起来的字母,像西班牙语里的ñ。这些语言最常用的编码方式是CP-1252,又叫做windows-1252,因为它在微软的视窗操作系统上被广泛使用。CP-1252和ASCII在0–127这个范围内的字符是一样的,但是CP-1252为ñ(n-with-a-tilde-over-it, 241),Ü(u-with-two-dots-over-it, 252)这类字符而扩展到了128–255这个范围。然而,它仍然是一种单字节的编码方式;可能的最大数字为255,这仍然可以用一个字节来表示。

然而,像中文,日语和韩语等语言,他们的字符如此之多而不得不需要多字节编码的字符集。即,使用两个字节的数字(0–255)代表每个字符。但是就跟不同的单字节编码方式一样,多字节编码方式之间也有同样的问题,即他们使用的数字是相同的,但是表达的内容却不同。相对于单字节编码方式它们只是使用的数字范围更广一些,因为有更多的字符需要表示。

在没有网络的时代,文本由自己输入,偶尔才会打印出来,大多数情况下使用以上的编码方案是可行的。那时没有太多的纯文本。源代码使用ASCII编码,其他人也都使用字处理器,这些字处理器定义了他们自己的格式(非文本的),这些格式会连同字符编码信息和风格样式一起记录其中,&c。人们使用与原作者相同的字处理软件读取这些文档,所以或多或少地能够使用。

现在,我们考虑一下像email和web这样的全球网络的出现。大量的“纯文本”文件在全球范围内流转,它们在一台电脑上被撰写出来,通过第二台电脑进行传输,最后在另外一台电脑上显示。计算机只能识别数字,但是这些数字可能表达的是其他的东西。Oh no! 怎么办呢。。好吧,那么系统必须被设计成在每一段“纯文本”上都搭载编码信息。记住,编码方式是将计算机可读的数字映射成人类可读的字符的解码密钥。失去解码密钥则意味着混乱不清的,莫名其妙的信息,或者更糟。

现在我们考虑尝试把多段文本存储在同一个地方,比如放置所有收到邮件的数据库。这仍然需要对每段文本存储其相关的字符编码信息,只有这样才能正确地显示它们。这很困难吗?试试搜索你的email数据库,这意味着需要在运行时进行编码之间的转换。很有趣是吧…

现在我们来分析另外一种可能性,即多语言文档,同一篇文档里来自几种不同语言的字符混在一起。(提示:处理这样文档的程序通常使用转义符在不同的模式(modes)之间切换。噗!现在是俄语 koi8-r 模式,所以241代表 Я;噗噗!现在到了Mac Greek模式,所以241代表 ώ。)当然,你也会想要搜索这些文档。根本就没有所谓的纯文本。

皮皮Blog



ASCII码

ASCII码一共规定了128个字符的编码,比如空格"SPACE"是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。

非ASCII编码

英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。

然而不同的国家有不同的字母,即使都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0--127表示的符号是一样的,不一样的只是128--255的这一段。

至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256x256=65536个符号。

虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。

皮皮Blog



Unicode编码

Unicode

如上,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。

可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。

Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字"严"。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表

Note:Unicode对象并没有编码。它们使用Unicode,一个一致的,通用的字符编码集。 当你在Python中处理Unicode对象的时候,你可以直接将它们混合使用和互相匹配而不必去考虑编码细节。


Unicode编码系统为表达任意语言的任意字符而设计。它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。(并不是所有的数字都用上了,但是总数已经超过了65535,所以2个字节的数字是不够用的。)被几种语言共用的字符通常使用相同的数字来编码,除非存在一个在理的语源学(etymological)理由使不这样做。不考虑这种情况的话,每个字符对应一个数字,每个数字对应一个字符。即不存在二义性。不再需要记录模式了。U+0041总是代表"A",即使这种语言没有"A"这个字符。

初次面对这个创想,它看起来似乎很伟大。一种编码方式即可解决所有问题。文档可包含多种语言。不再需要在各种编码方式之间进行模式转换。但是很快,一个明显的问题跳到我们面前。4个字节?只为了单独一个字符 这似乎太浪费了,特别是对像英语和西语这样的语言,他们只需要不到1个字节即可以表达所需的字符。事实上,对于以象形为基础的语言(比如中文)这种方法也有浪费,因为这些语言的字符也从来不需要超过2个字节即可表达。

有一种Unicode编码方式每1个字符使用4个字节。它叫做UTF-32,因为32位 = 4字节。UTF-32是一种直观的编码方式;它收录每一个Unicode字符(4字节数字)然后就以那个数字代表该字符。这种方法有其优点,最重要的一点就是可以在常数时间内定位字符串里的第N个字符,因为第N个字符从第4×Nth个字节开始。另外,它也有其缺点,最明显的就是它使用4个诡异的字节来存储每个诡异的字符…

尽管有Unicode字符非常多,但是实际上大多数人不会用到超过前65535个以外的字符。因此,就有了另外一种Unicode编码方式,叫做UTF-16(因为16位 = 2字节)。UTF-16将0–65535范围内的字符编码成2个字节,如果真的需要表达那些很少使用的星芒层(astral plane)内超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现。UTF-16编码最明显的优点是它在空间效率上比UTF-32高两倍,因为每个字符只需要2个字节来存储(除去65535范围以外的),而不是UTF-32中的4个字节。并且,如果我们假设某个字符串不包含任何星芒层中的字符,那么我们依然可以在常数时间内找到其中的第N个字符,直到它不成立为止这总是一个不错的推断…

但是对于UTF-32和UTF-16编码方式还有一些其他不明显的缺点。不同的计算机系统会以不同的顺序保存字节。这意味着字符U+4E2D在UTF-16编码方式下可能被保存为4E 2D或者2D 4E,这取决于该系统使用的是大尾端(big-endian)还是小尾端(little-endian)。(对于UTF-32编码方式,则有更多种可能的字节排列。)只要文档没有离开你的计算机,它还是安全的 -- 同一台电脑上的不同程序使用相同的字节顺序(byte order)。但是当我们需要在系统之间传输这个文档的时候,也许在万维网中,我们就需要一种方法来指示当前我们的字节是怎样存储的。不然的话,接收文档的计算机就无法知道这两个字节4E 2D表达的到底是U+4E2D还是U+2D4E

为了解决这个问题,多字节的Unicode编码方式定义了一个字节顺序标记(Byte Order Mark),它是一个特殊的非打印字符,你可以把它包含在文档的开头来指示你所使用的字节顺序。对于UTF-16,字节顺序标记是U+FEFF。如果收到一个以字节FF FE开头的UTF-16编码的文档,你就能确定它的字节顺序是单向的(one way)的了;如果它以FE FF开头,则可以确定字节顺序反向了。

不过,UTF-16还不够完美,特别是要处理许多ASCII字符时。如果仔细想想的话,甚至一个中文网页也会包含许多的ASCII字符 -- 所有包围在可打印中文字符周围的元素(element)和属性(attribute)。能够在常数时间内找到第Nth个字符当然非常好,但是依然存在着纠缠不休的星芒层字符的问题,这意味着你不能保证每个字符都是2个字节长,所以,除非你维护着另外一个索引,不然就不能真正意义上的在常数时间内定位第N个字符。另外,朋友,世界上肯定还存在很多的ASCII文本。

Unicode的问题

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

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

这里就有两个严重的问题

第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?

第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:

1)出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示Unicode。

2)Unicode在很长一段时间内无法推广,直到互联网的出现。

为了更好的理解,一般你可以认为unicode是没有编码的,只是一种表示;unicode进行encode编码后才成为utf-8, gbk等等,同时utf-8, gbk等等解码后就变成unicode。


皮皮Blog


另外一些人琢磨着这些问题,他们找到了一种解决方法:

UTF-8

UTF-8是一种为Unicode设计的变长(variable-length)编码系统。即,不同的字符可使用不同数量的字节编码。对于ASCII字符(A-Z,&c.)UTF-8仅使用1个字节来编码。事实上,UTF-8中前128个字符(0–127)使用的是跟ASCII一样的编码方式。像ñ和ö这样的扩展拉丁字符(Extended Latin)则使用2个字节来编码。(这里的字节并不是像UTF-16中那样简单的Unicode编码点(unicode code point);它使用了一些位变换(bit-twiddling)。)中文字符比如则占用了3个字节。很少使用的星芒层字符则占用4个字节。

缺点:因为每个字符使用不同数量的字节编码,所以寻找串中第N个字符是一个O(N)复杂度的操作 -- 即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。

优点:在处理经常会用到的ASCII字符方面非常有效。在处理扩展的拉丁字符集方面也不比UTF-16差。对于中文字符来说,比UTF-32要好。同时,(在这一条上你得相信我,因为我不打算给你展示它的数学原理。)由位操作的天性使然,使用UTF-8不再存在字节顺序的问题了。一份以UTF-8编码的文档在不同的计算机之间是一样的比特流。


互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种Unicode的实现方式。

其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。UTF-8是Unicode的实现方式之一。

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

UTF-8的编码规则

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

跟据上表,解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

下面,还是以汉字"严"为例,演示如何实现UTF-8编码。

已知"严"的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此"严"的UTF-8编码需要三个字节,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"严"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,"严"的UTF-8编码是"11100100 10111000 10100101",转换成十六进制就是E4B8A5。

Unicode与UTF-8之间的转换

通过上一节的例子,可以看到"严"的Unicode码是4E25{unicode表示是这么表示,对于全局可能不是最优的吧?},UTF-8编码是E4B8A5{unicode的一种比较优化的表示?},两者是不一样的。它们之间的转换可以通过程序实现。

在Windows平台下,有一个最简单的转化方法,就是使用内置的记事本小程序Notepad.exe。打开文件后,点击"文件"菜单中的"另存为"命令,会跳出一个对话框,在最底部有一个"编码"的下拉条。

bg2007102801.jpg

里面有四个选项:ANSI,Unicode,Unicode big endian 和 UTF-8。

1)ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。

2)Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式。

3)Unicode big endian编码与上一个选项相对应。我在下一节会解释little endian和big endian的涵义。

4)UTF-8编码,也就是上一节谈到的编码方法。

选择完"编码方式"后,点击"保存"按钮,文件的编码方式就立刻转换好了。

当然使用notepad++等更高级的文本编辑器转换更简单,自己可以试试。

皮皮Blog



中文字符编码标准

GB2312

1980年,中国制定了GB2312-80,一共收录了 7445 个字符,包括 6763 个汉字和 682 个其它符号。

GB2312-80,简称为GB2312。

在 Windows 中的代码页(Code Page)是 CP936。

GBK

微软,对GB2312-80的扩展,即利用GB 2312-80未使用的编码空间,收录所有的GB 13000.1-93和Unicode 1.1之中的汉字全部字符,制定了GBK编码。

GBK 收录了 21886 个符号,它分为汉字区和图形符号区。汉字区包括 21003 个字符。

GBK 作为对 GB2312 的扩展,在现在的 Windows 系统中仍然使用代码页 CP936 表示,但是同样的 936 的代码页跟一开始的 936 的代码页只支持 GB2312 编码不同,现在的 936 代码页支持 GBK 的编码,GBK 同时也向下兼容GB2312 编码。

所以,技术编码上,GBK兼容旧的GB2312,但是编码方式和GB13000不同,不兼容GB13000,但是所包含文字上,算是和GB13000相同。

技术编码方面上,演化顺序为:ASCII ⇒ GB2312 ⇒ GBK ⇒ GB18030

中文字符相关编码标准

编码标准别名标准所属包含字符
ASCII 国际通用 
GB2312微软Windows中以前的CP936中国大陆6763 个汉字和 682 个其它符号
Unicode 1.1 国际通用20,902个字符
GB13000 中国大陆20,902个字符
GBK微软Windows中现在的CP936微软21886 个符号
GB18030微软Windows中的CP54936中国大陆27484 个汉字+其他少数民族字符

皮皮Blog



如何确定文本文件的编码

[http://my.oschina.net/goldenshaw/blog/413412]

 字符(存储)交换标准

from:http://blog.csdn.net/pipisorry/article/details/42387045

ref: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets

字符编码详解

字符编码笔记

[Emoji Unicode Tables]


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值