再次看编码

转载:http://techmi.cn/archives/你不知道的字符集和编码.htmlhtml

         http://news.cnblogs.com/n/139987/node

 

其实即便是如今对编码仍是很糊,缘由不少概念的混乱,不明晰致使的。python

常说的字符集和编码区别,其实就是编码字符集和字符集编码的区别,其实,单单若是只是说字符集,没有任何编码的概念的话,那么字符集其实仅仅是一个简单的字符的集合,或者说是一个抽象的字符的集合,包括文字,符号等等,不参与任何存储形式,只是存在这么各类各样标准的字符的集合。linux

若是仅仅是抽象的字符集,咱们是无需拿出讨论的,由于没有任何异议,通俗易懂,而常说的字符集指的编码字符集,好比常见的 unicode、ascii、gb23十二、gbk等,这些咱们常称作为字符集(实际上是编码字符集),这些字符集,好比unicode其实本质上是已经“编码”过的字符集,即每一个字符都有惟一的整数编号,每一个字符都有本身特有的编号,同一个字符在不一样编码字符集中编号也会不一样,固然不少编码字符集都是ascll的超集,因此ascll字符集的编号与不少编码字符集中编号都同样,好比英文字母“A”,在ASCII及Unicode及GB2312中,均是第0×41个字符,说到这里朋友必定注意到了我上面再描述“ unicode其实本质上是已经“编码”过的字符集”中的“编码”二字加了双引号,我要强调的是这里的“编码”并非真的我下面要说的编码,这里只是为每一个字符编了一个对应的编号,可是咱们仍是习惯专业的称呼为“编码字符集”面试

咱们常常说“文章采用的是utf-8编码方式”shell

我对于这个编码方式的意义,我的理解是 将一个字符的整数编号用一个什么二进制的整数值来对应并在计算机存储。这和上面说的编码字符集中的“编码”千差万别,这里咱们称之为“字符集编码”,即咱们常说的编码vim

说到这里,不少人会以为那么unicode和utf-8的区别在哪里?既然上文说到unicode是编码字符集,那么utf-8又是什么?就是常说的编码?sublime-text

“文章采用的是utf-8编码方式”,我的以为准确的说法是“文章采用的是基于unicode编码字符集的utf-8的编码方案”,即数组

即unicode自己做为编码字符集没有任何存储形式,只是一个编号和字符对应的表而已,如何在计算机存储?你可能想到了干脆直接把编号看成二进制数值来直接存储,那么为何不这么作呢?这也算是一种字符集编码方案,就是基于unicode编码字符集的utf-32编码方案,那么有没有更加智能一点的编码方案呢?为何会没有呢?那就是utf-八、utf-16等等, 等等,在我解释为什么要用utf-8编码方案的时候,我必须说明一件事情:以下浏览器

我在上一篇文章《你不知道的 页面编码,浏览器选择编码,get,post各类乱码由来》中说过:“如何查看中文字符的十六进制字符串?方法:BitConverter.ToString(System.Text.Encoding.UTF8.GetBytes(“阿道夫”));” 请注意我能够改成“System.Text.Encoding.Unicode.GetBytes” 以下图是vs2013 Encoding键入“.”后的智能提示

zifuji20141112_1

zifuji20141112_2

(列表过长,用两幅图分别截图)

上图有两个疑问:

一、若是说unicode是编码字符集,为什么会出如今和utf-8这种编码方案并列的列表中?

二、ASCII或者gb2312都是编码字符集为什么也会出如今和utf-8这种编码方案并列的列表中?

咱们假设有两个猜想:

一、此处的unicode并非真正的unicode编码字符集,可能只是一种和unicode编码字符集关系很是紧密的一种编码方案

二、ASCII或者gb2312(其实就是图中的Default,即操做系统当前的编码,国内通常为gb2312)是编码字符集没有错,可是对于ASCII或者gb2312都只有惟一一种编码,那么我称呼它们为ASCII编码或者GB2312编码也没有问题,既然这样,那我把ascii和gb2312加入和utf-8这种编码方案并列的列表中也理所固然?

个人两个假设,很快获得论证

一、在Encoding 的元数据看到:

这里解释在这里的unicode其实本质上“获取使用 Little-Endian 字节顺序的 UTF-16 格式的编码”,即便基于unicode编码字符集的utf-16编码方案,相似的有BigEndianUnicode(获取使用 Big Endian 字节顺序的 UTF-16 格式的编码)

二、通常的ASCII或者gb2312,咱们能够称呼为ASCII字符集也能够称呼为ASCII编码,只是意义不一样而已,由于对于ASCII编码字符集或者gb2312编码字符集都只有惟一一种编码,就是ASCII编码和GB2312编码,那么列表中显示的ASCII和GB2312指的不是编码字符集而是ASCII和GB2312的编码方案,我想正是这种缘由,才在不少时候,无论是字符集赋值仍是编码方案赋值均可以直接用gb2312或者ascii,好比:

Encoding gb2312 = Encoding.GetEncoding(“gb2312″);

Response.ContentEncoding = gb2312;//编码

Response.Charset=”gb2312″;//字符集

总结下的说:

就是unicode是字符集,不是编码!可是ascii(gb2312)是字符集,这个说法确定正确,可是我表达为“ascii编码”也不能说大错特错,可是这种说法让人误解,若是必定要说那么就说“ascii编码字符集的编码”

若是理解上面两个假设的论证道理,那么咱们继续讨论以前暂停的话题,即“解释为什么要用utf-8等编码方案(其余utf编码方案相似)”

utf-8将很大一部分基于unicode编码字符集的字符的整数编号做了变换后存储在计算机中。(引用)以“汉”字为例,“汉”的Unicode值为0x6C49,但其编码为UTF-8格式后的值为0xE6B189(注意到变成了三个字节)。对于UTF-16编码方案,则是对unicode编码字符集中的前65536个字符编号都不作变换,直接做为计算机存储时使用的值(对65536之后的字符,仍然要作变换),例如“汉”字的Unicode编号为0x6C49,那么通过UTF-16编码后存储在计算机上时,它的表示仍为0x6C49,对于UTF-32编码方案,他对全部的Unicode字符均不作变换,直接使用编号存储,只是这种编码方案太浪费存储空间(就连1个字节就能够搞定的英文字符,它都必须使用4个字节)

既然unicode编码字符集有如此多的编码方案,那么

utf-8,字母数字符号等占1字节,汉字占三字节

utf-16,对unicode编码字符集中的前65536个字符都占两个字节

utf-32,所有占四字节

若是还有人问:

“unicode编码每一个字符占几个字节”,咱们能够义正词严的说,第一unicode不是编码!第二每一个字符具体占多少字节是要看编码方案!

不少面试题会问:

那么答案就是12和6了

最后,对于gb2312或者ascii编码字符集的字符的编号就是直接存储在计算机中的二进制数,也就是说gb2312和ascii编码字符集都只有一种编码方案,由于在gb2312编码字符集中的ascii字符集部分的编号并无变化(即和ascii编码字符集中的编码一致),因此gb2312的ascii部分字符存入计算机的二进制数仍是占用1个字节,而中文字符存入计算机的二进制数也是该中文字符在gb2312编码字符集中的编号,该编号通常转换成二进制数都占两个字节,这个过程也就变成了所谓的gb2312编码

若是上面的改成System.Text.Encoding.Default.GetBytes(param).Length,则值就是9和6了

 

##################################################################################

 

编码问题的例子

  在 Windows 自带的 Notepad(记事本)程序中输入“联通”两个字,保存后再次打开,会发现“联通”不见了,代之以“��ͨ”的乱码。这是 Windows 平台上典型的中文编码问题。即文件保存的时候是按照 ANSI 编码(其实就是 GB2312,后面会详细介绍)保存,打开的时候程序按照 UTF-8方式对内容解释,因而就出现了乱码。避免乱码的方式很简单,在“文件”菜单中选择“打开”命令,选择保存的文件,而后选择“ANSI”编码,此时就能看到久违的“联通”两个字了。

  在 Linux 平台上若是使用 cat 等命令查看文件中的中文内容时,可能出现乱码。这也是编码的问题。简单的说是文件时按照A编码保存,可是 cat 命令按照当前 Locale 设定的B编码去查看,在B和A不兼容的时候就出现了乱码。

  为何写这篇文章

  中文编码因为历史缘由牵扯到很多标准,在不了解的时候感受一头雾水;但其实理解编码问题并不须要你深刻了解各个编码标准,只要你明白了前因后果,了解了关键的知识点,就能分析和解决平常开发工做中碰到的大部分编码问题。有感于我看过的资料和文章要么不够全面,要么略显枯燥,因此经过这篇文章记录下笔者在平常工做中碰到的中文编码原理相关问题,目的主要是自我总结,若是能给读者提供一些帮助那就算是意外之喜了。因为严谨的编码标准对我来讲是无趣的,枯燥的,难以记忆的,本文尝试用浅显易懂的生活语言解释中文编码相关的(也可能不相关的)一些问题,这也是为何取名杂谈的缘由。本文确定存在不规范不全面的地方,我会在参考资料里给出官方文档的连接,也欢迎读者在评论中提出更好的表达方式&指出错误,不胜感激。

  对编码问题的理解我认为分为三个层次,第一个层次:概念,知道各个编码标准的应用场景,了解之间的差别,能分析和解决常见的一些编码问题。第二个层次:标准,掌握编码的细节,如编码范围,编码转换规则,知道这些就能自行开发编码转换工具。第三个层次,使用,了解中文的编码二进制存储,在程序开发过程当中选择合理的编码并处理中文。为了不让读者陷入编码标准的黑洞没法脱身(不相信?看看 unicode 的规范就明白个人意思了),同时因为编码查询&转换工具等都有现成工具可使用,本文只涉及第一个层次,不涉及第二层次,在第三层次上会作一些尝试。在本文的最后提供了相关连接供对标准细节感兴趣的同窗继续学习。最后,本文不涉及具体软件的乱码问题解决,如 ssh,shell,vim,screen 等,这些话题留给剑豪同窗专文阐述。

  一切都是由于电脑不识字

  电脑很聪明,能够帮咱们作不少事情,最开始主要是科学计算,这也是为何电脑别名计算机。电脑又很笨,在她的脑子里只有数字,即全部的数据在存储和运算时都要使用二进制数表示。这在最初电脑主要用来处理大量复杂的科学计算时不是什么大问题,可是当电脑逐步走入普通人的生活时,状况开始变糟了。办公自动化等领域最主要的需求就是文字处理,电脑如何来表示文字呢?这个问题固然难不倒聪明的计算机科学家们,用数字来表明字符呗。这就是“编码”。

  英文的终极解决方案:ASCII

  每一个人均可以约定本身的一套编码,只要使用方之间了解就 ok 了。好比说咱俩约定0×10表示a,0×11表示b。在一开始也的确是这样的,出现了各式各样的编码。这样有两个问题:1. 各个编码的字符集不同,有的多,有的少。2. 相同字符的编码也不同。你这里a是0×10,他那里a多是0×30。因而你保存的文件他就不能直接用,必需要转换编码。随着沟通范围的扩大,采用不一样编码的人们互相通讯就乱套了,这就是咱们常说的:鸡同鸭讲。若是要避免这种混乱,那么你们就必须使用相同的编码规则,因而美国有关的标准化组织就出台了 ASCII(American Standard Code for Information Interchange)编码,统一规定了英文经常使用符号用哪些二进制数来表示。ASCII 是标准的单字节字符编码方案,用于基于文本的数据。

  ASCII 最初是美国国家标准,供不一样计算机在相互通讯时用做共同遵照的西文字符编码标准,已被国际标准化组织(International Organization for Standardization, ISO)定为国际标准,称为 ISO 646 标准。适用于全部拉丁文字字母。ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符。标准 ASCII 码也叫基础 ASCII 码,使用 7 位二进制数来表示全部的大写和小写字母,数字 0 到九、标点符号, 以及在美式英语中使用的特殊控制字符。而最高位为 1 的另 128 个字符(80H—FFH)被称为“扩展 ASCII”,通常用来存放英文的制表符、部分音标字符等等的一些其它符号。

  其中:031127(33)是控制字符或通讯专用字符(其他为可显示字符),32~126(共 95 个)是字符(32是空格),其中 48~57为 0 到 9 十个阿拉伯数字,65~90为 26 个大写英文字母,97~122号为 26 个小写英文字母,其他为一些标点符号、运算符号等。

  如今全部使用英文的电脑终于能够用同一种编码来交流了。理解了 ASCII 编码,其余字母型的语言编码方案就举一反三了。

  一波三折的中文编码

  第一次尝试:GB2312

  ASCII 这种字符编码规则显然用来处理英文没有什么问题,它的出现极大的促进了信息在西方尤为是美国的传播和交流。可是对于中文,经常使用汉字就有 6000 以上,ASCII 单字节编码显然是不够用。为了粉碎美帝国主义经过编码限制中国人民使用电脑的无耻阴谋,中国国家标准总局发布了 GB2312 码即中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码字符集——基本集》,1981年 5 月 1 日实施,通行于大陆。GB2312字符集中除经常使用简体汉字字符外还包括希腊字母、日文平假名及片假名字母、俄语西里尔字母等字符,未收录繁体中文汉字和一些生僻字。 EUC-CN 能够理解为 GB2312 的别名,和 GB2312 彻底相同。

  GB2312是基于区位码设计的,在区位码的区号和位号上分别加上 A0H 就获得了 GB2312 编码。这里第一次提到了“区位码”,我就连带把下面这几个让人摸不到头脑的 XX 码一锅端了吧:

  区位码,国标码,交换码,内码,外码

  区位码:就是把中文经常使用的符号,数字,汉字等分门别类进行编码。区位码把编码表分为 94 个区,每一个区对应 94 个位,每一个位置就放一个字符(汉字,符号,数字都属于字符)。这样每一个字符的区号和位号组合起来就成为该汉字的区位码。区位码通常用 10 进制数来表示,如 4907 就表示 49 区 7 位,对应的字符是“学”。区位码中 01-09区是符号、数字区,16-87区是汉字区,10-15和 88-94是未定义的空白区。它将收录的汉字分红两级:第一级是经常使用汉字计 3755 个,置于 16-55区,按汉语拼音字母/笔形顺序排列;第二级汉字是次经常使用汉字计 3008 个,置于 56-87区,按部首/笔画顺序排列。在网上搜索“区位码查询系统”能够很方便的找到汉字和对应区位码转换的工具。为了不广告嫌疑和死链,这里就不举例了。

  国标码: 区位码没法用于汉字通讯,由于它可能与通讯使用的控制码(00H~1FH)(即0~31,还记得 ASCII 码特殊字符的范围吗?)发生冲突。因而 ISO2022 规定每一个汉字的区号和位号必须分别加上 32(即二进制数 00100000,16进制 20H),获得对应的国标交换码,简称国标码,交换码,所以,“学”字的国标交换码计算为:

  00110001 00000111
+ 00100000 00100000
 -------------------
  01010001 00100111 

  用十六进制数表示即为 5127H。

  交换码:即国标交换码的简称,等同上面说的国标码。

  内码:因为文本中一般混合使用汉字和西文字符,汉字信息若是不予以特别标识,就会与单字节的 ASCII 码混淆。此问题的解决方法之一是将一个汉字当作是两个扩展 ASCII 码,使表示 GB2312 汉字的两个字节的最高位都为1。即国标码加上 128(即二进制数 10000000,16进制 80H)这种高位为 1 的双字节汉字编码即为 GB2312 汉字的机内码,简称为内码。20H+80H=A0H。这也就是常说的在区位码的区号和位号上分别加上 A0H 就获得了 GB2312 编码的由来。

  00110001 00000111
+ 10100000 10100000
  -------------------
  11010001 10100111 

  用十六进制数表示即为 D1A7H。

  外码:机外码的简称,就是汉字输入码,是为了经过键盘字符把汉字输入计算机而设计的一种编码。 英文输入时,相输入什么字符便按什么键,外码和内码一致。汉字输入时,可能要按几个键才能输入一个汉字。 汉字输入方案有成百上千个,可是这千差万别的外码输入进计算机后都会转换成统一的内码。

  最后总结一下上面的概念。中国国家标准总局把中文经常使用字符编码为 94 个区,每一个区对应 94 个位,每一个字符的区号和位号组合起来就是该字符的区位码, 区位码用 10 进制数来表示,如 4907 就表示 49 区 7 位,对应的字符是“学”。 因为区位码的取值范围与通讯使用的控制码(00H~1FH)(即0~31)发生冲突。每一个汉字的区号和位号分别加上 32(即 16 进制 20H)获得国标码,交换码。“学”的国标码为 5127H。因为文本中一般混合使用汉字和西文字符,为了让汉字信息不会与单字节的 ASCII 码混淆,将一个汉字当作是两个扩展 ASCII 码,即汉字的两个字节的最高位置为1,获得的编码为 GB2312 汉字的内码。“学”的内码为 D1A7H。不管你使用什么输入法,经过什么样的按键组合把“学”输入计算机,“学”在使用 GB2312(以及兼容 GB2312)编码的计算机里的内码都是 D1A7H。

  第二次尝试:GBK

  GB2312的出现基本知足了汉字的计算机处理须要,但因为上面提到未收录繁体字和生僻字,从而不能处理人名、古汉语等方面出现的罕用字,这致使了 1995 年《汉字编码扩展规范》(GBK)的出现。GBK 编码是 GB2312 编码的超集,向下彻底兼容 GB2312,兼容的含义是不只字符兼容,并且相同字符的编码也相同,同时在字汇一级支持 ISO/IEC10646—1和 GB 13000—1的所有中、日、韩(CJK)汉字,共计 20902 字。GBK 还收录了 GB2312 不包含的汉字部首符号、竖排标点符号等字符。CP936和 GBK 的有些许差异,绝大多数状况下能够把 CP936 看成 GBK 的别名。

  第三次尝试:GB18030

  GB18030编码向下兼容 GBK 和 GB2312。GB18030收录了全部 Unicode3.1 中的字符,包括中国少数民族字符,GBK 不支持的韩文字符等等,也能够说是世界大多民族的文字符号都被收录在内。GBK 和 GB2312 都是双字节等宽编码,若是算上和 ASCII 兼容所支持的单字节,也能够理解为是单字节和双字节混合的变长编码。GB18030编码是变长编码,有单字节、双字节和四字节三种方式。

  其实,这三个标准并不须要死记硬背,只须要了解是根据应用需求不断扩展编码范围便可。从 GB2312 到 GBK 再到 GB18030 收录的字符愈来愈多便可。万幸的是一直是向下兼容的,也就是说一个汉字在这三个编码标准里的编码是如出一辙的。这些编码的共性是变长编码,单字节 ASCII 兼容,对其余字符 GB2312 和 GBK 都使用双字节等宽编码,只有 GB18030 还有四字节编码的方式。这些编码最大的问题是 2 个。1. 因为低字节的编码范围和 ASCII 有重合,因此不能根据一个字节的内容判断是中文的一部分仍是一个独立的英文字符。2. 若是有两个汉字编码为 A1A2B1B2,存在 A2B1 也是一个有效汉字编码的特殊状况。这样就不能直接使用标准的字符串匹配函数来判断一个字符串里是否包含某一个汉字,而须要先判断字符边界而后才能进行字符匹配判断。

  最后,提一个小插曲,上面讲的都是大陆推行的汉字编码标准,使用繁体的中文社群中最经常使用的电脑汉字字符集标准叫大五码(Big5),共收录 13,060个中文字,其中有二字为重覆编码(实在是不该该)。Big5虽普及于中国的台湾、香港与澳门等繁体中文通行区,但长期以来并不是当地的国家标准,而只是业界标准。倚天中文系统、Windows 等主要系统的字符集都是以 Big5 为基准,但厂商又各自增删,衍生成多种不一样版本。2003年,Big5被收录到台湾官方标准的附录当中,取得了较正式的地位。这个最新版本被称为 Big5-2003。

  天下归一 Unicode

  看了上面的多个中文编码是否是有点头晕了呢?若是把这个问题放到全世界n多个国家n多语种呢?各国和各地区本身的文字编码规则互相冲突的状况全球信息交换带来了很大的麻烦。

  要真正完全解决这个问题,上面介绍的那些经过扩展 ASCII 修修补补的方式已经走不通了,而必须有一个全新的编码系统,这个系统要能够将中文、日文、法文、德文……等等全部的文字统一块儿来考虑,为每个文字都分配一个单独的编码。因而,Unicode 诞生了。Unicode(统一码、万国码、单一码)为地球上(之后会包括火星,金星,喵星等)每种语言中的每一个字符设定了统一而且惟一的二进制编码,以知足跨语言、跨平台进行文本转换、处理的要求。在 Unicode 里,全部的字符被一视同仁,汉字再也不使用“两个扩展 ASCII”,而是使用“1个 Unicode”来表示,也就是说,全部的文字都按一个字符来处理,它们都有一个惟一的 Unicode 码。Unicode 用数字0-0x10FFFF 来映射这些字符,最多能够容纳 1114112 个字符,或者说有 1114112 个码位(码位就是能够分配给字符的数字)。

  提到 Unicode 不能不提 UCS(通用字符集 Universal Character Set)。UCS 是由 ISO 制定的 ISO 10646(或称 ISO/IEC 10646)标准所定义的标准字符集。UCS-2用两个字节编码,UCS-4用 4 个字节编码。Unicode 是由 unicode.org 制定的编码机制,ISO 与 unicode.org 是两个不一样的组织, 虽然最初制定了不一样的标准; 但目标是一致的。因此自从 Unicode 2.0 开始, Unicode 采用了与 ISO 10646-1相同的字库和字码, ISO 也承诺 ISO10646 将不会给超出 0x10FFFF 的 UCS-4编码赋值, 使得二者保持一致。你们简单认为 UCS 等同于 Unicode 就能够了。

  在 Unicode 中:汉字“字”对应的数字是 23383。在 Unicode 中,咱们有不少方式将数字 23383 表示成程序中的数据,包括:UTF-八、UTF-1六、UTF-32。UTF 是“UCS Transformation Format”的缩写,能够翻译成 Unicode 字符集转换格式,即怎样将 Unicode 定义的数字转换成程序数据。例如,“汉字”对应的数字是 0x6c49 和 0x5b57,而编码的程序数据是:

BYTE data_utf8[] = {0xE6, 0xB1, 0x89, 0xE5, 0xAD, 0x97}; // UTF-8编码
WORD data_utf16[] = {0x6c49, 0x5b57}; // UTF-16编码
DWORD data_utf32[] = {0x6c495b57}; // UTF-32编码 

  这里用 BYTE、WORD、DWORD 分别表示无符号 8 位整数,无符号 16 位整数和无符号 32 位整数。UTF-八、UTF-1六、UTF-32分别以 BYTE、WORD、DWORD 做为编码单位。“汉字”的 UTF-8编码须要 6 个字节。“汉字”的 UTF-16编码须要两个 WORD,大小是 4 个字节。“汉字”的 UTF-32编码须要两个 DWORD,大小是 8 个字节。根据字节序的不一样,UTF-16能够被实现为 UTF-16LE 或 UTF-16BE,UTF-32能够被实现为 UTF-32LE 或 UTF-32BE。

  下面介绍 UTF-八、UTF-1六、UTF-3二、BOM。

  UTF-8

  UTF-8以字节为单位对 Unicode 进行编码。从 Unicode 到 UTF-8的编码方式以下:  

Unicode 编码(16进制) UTF-8 字节流(二进制)
000000 – 00007F 0xxxxxxx
000080 – 0007FF 110xxxxx 10xxxxxx
000800 – 00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000 – 10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

  UTF-8的特色是对不一样范围的字符使用不一样长度的编码。对于0×00-0x7F 之间的字符,UTF-8编码与 ASCII 编码彻底相同。UTF-8编码的最大长度是 4 个字节。从上表能够看出,4字节模板有 21 个x,便可以容纳 21 位二进制数字。Unicode 的最大码位 0x10FFFF 也只有 21 位。总结了一下规律:UTF-8的第一个字节开始的 1 的个数表明了总的编码字节数,后续字节都是以 10 开始。由上面的规则能够清晰的看出 UTF-8编码克服了中文编码的两个问题。

  例1:“汉”字的 Unicode 编码是 0x6C49。0x6C49在0×0800-0xFFFF 之间,使用 3 字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将 0x6C49 写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,获得:11100110 10110001 10001001,即 E6 B1 89。

  例2:Unicode 编码 0x20C30 在0×010000-0x10FFFF 之间,使用用 4 字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将 0x20C30 写成 21 位二进制数字(不足 21 位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,获得:11110000 10100000 10110000 10110000,即 F0 A0 B0 B0。

  UTF-16

  UTF-16编码以 16 位无符号整数为单位。咱们把 Unicode 编码记做U。编码规则以下:若是U<0×10000,U的 UTF-16编码就是U对应的 16 位无符号整数(为书写简便,下文将 16 位无符号整数记做 WORD)。中文范围 4E00-9FBF,因此在 UTF-16编码里中文 2 个字节编码。若是U≥0×10000,咱们先计算U’=U-0×10000,而后将U’写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的 UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

  UTF-32

  UTF-32编码以 32 位无符号整数为单位。Unicode 的 UTF-32编码就是其对应的 32 位无符号整数。

  字节序

  根据字节序(对字节序不太了解的同窗请参考 http://en.wikipedia.org/wiki/Endianness)的不一样,UTF-16能够被实现为 UTF-16LE(Little Endian)或 UTF-16BE(Big Endian),UTF-32能够被实现为 UTF-32LE 或 UTF-32BE。例如:  

Unicode 编码 UTF-16LE UTF-16BE UTF-32LE UTF-32BE
0x006C49 49 6C 6C 49 49 6C 00 00 00 00 6C 49
0x020C30 43 D8 30 DC D8 43 DC 30 30 0C 02 00 00 02 0C 30

  那么,怎么判断字节流的字节序呢?Unicode 标准建议用 BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被做为 BOM 的字符”零宽无中断空格”。这个字符的编码是 FEFF,而反过来的 FFFE(UTF-16)和 FFFE0000(UTF-32)在 Unicode 中都是未定义的码位,不该该出如今实际传输中。下表是各类 UTF 编码的 BOM:  

UTF 编码 Byte Order Mark
UTF-8 EF BB BF
UTF-16LE FF FE
UTF-16BE FE FF
UTF-32LE FF FE 00 00
UTF-32BE 00 00 FE FF

  总结一下,ISO 与 unicode.org 都敏锐的意识到只有为世界上每种语言中的每一个字符设定统一而且惟一的二进制编码才能完全解决计算机世界信息交流中编码冲突的问题。由此诞生了 UCS 和 Unicode,而这两个规范是一致的。在 Unicode 里,全部的字符被一视同仁,也就是说,全部的文字都按一个字符来处理,它们都有一个惟一的 Unicode 码。UTF-八、UTF-1六、UTF-32分别定义了怎样将 Unicode 定义的数字转换成程序数据。UTF-8以字节为单位对 Unicode 进行编码,一个英文字符占 1 个字节,汉字占 3 个字节;UTF-16以 16 位无符号整数为单位对 Unicode 进行编码,中文英文都占 2 个字节;UTF-32以 32 位无符号整数为单位对 Unicode 进行编码,中文英文都占 4 个字节。能够在 http://www.unicode.org/charts/unihan.html 查看汉字的 unicode 码以及 UTF-八、UTF-1六、UTF-32编码。

  中文二进制存储

  介绍了这么多的编码知识,真正的文件内容是什么样子的呢?下面咱们就经过实验看看在笔者 Linux 机器上 “中文”这两个字在不一样的编码下保存的文件内容。下面是个人实验过程,有兴趣的同窗能够在本身的机器上重作一下。Window 平台上的状况相似这里就不赘述了。

  实验须要须要使用 2 个工具:

  1. od 查看文件内容:http://www.gnu.org/software/coreutils/manual/html_node/od-invocation.html
  2. iconv 编码转换工具:http://www.gnu.org/software/libiconv/
汉字 Unicode(ucs-2)10进制表示 Utf-8 Utf-16 Utf32 区位码 GB2312/GBK/GB18030
20013 E4 B8 AD 4E2D 00004E2D 5448 D6D0
25991 E6 96 87 6587 00006587 4636 CEC4

  机器环境:

  OS: Red Hat Enterprise Linux AS release 4

  CPU: Intel (R) Xeon (R) CPU

  Locale:LC_ALL=zh_CN.utf-8

//生成 utf8 编码下的文件
echo –n "中文" > foo.utf8
 
//检查 foo 的内容:
od -t x1 foo.utf8
0000000 e4 b8 ad e6 96 87
 
//转换为 utf16 编码
iconv -f utf-8 -t utf-16 foo.utf8 > foo.utf16
 
//查看 foo.utf16 内容
od -t x1 foo.utf16
0000000 ff fe 2d 4e 87 65
Ff fe 是 BOM(还记得吗?经过 BOM 来字节流的字节序),其他部分的确是 UTF-16LE 编码的内容
 
//转换为 utf32 编码
iconv -f utf-16 -t utf-32 foo.utf16 > foo.utf32
 
//查看 foo.utf32 内容
od -t x1 foo.utf32
0000000 ff fe 00 00 2d 4e 00 00 87 65 00 00
Ff fe 是 BOM,的确是 UTF-32LE 编码的内容
 
//转换为 gb2312 编码
iconv -f utf-8 -t gb2312 foo.txt > foo.gb2312
od -t x1 foo.gb2312
0000000 d6 d0 ce c4
 
//转换为 GBK 编码
iconv -f utf-8 -t gbk foo.txt > foo.gbk
od -t x1 foo.gbk
0000000 d6 d0 ce c4
 
//转换为 GB18030 编码
iconv -f utf-8 -t gb18030 foo.txt > foo.gb18030
od -t x1 foo.gb18030
0000000 d6 d0 ce c4 

  C语言中文处理

  先明确一个概念:程序内部编码和程序外部编码。程序内部编码指的是中文字符在程序运行时在内存中的编码形式。程序外部编码则是中文字符在存储或者传输时的编码形式。程序外部编码的最直观的例子就是当把中文存储到硬盘文件中时选择的编码。

  根据程序内部编码和程序外部编码是否一致,C/C++的中文处理有两种常见的方式:

  1. 内外编码相同。输入输出时不须要考虑编码转换,程序内部处理时把中文字符当作普通的 2 进制数据流进行处理。
  2. 内外编码不一样。输入输出的时候根据应用须要选择合适的编码格式进行编码转换;程序内部统一编码处理。

  方法 1 的优势不言而喻,因为内外统一,不须要进行转换。不足是若是不是C标准库支持的编码方式,那么字符串处理函数须要本身实现。好比说标准 strlen 函数不能计算中文编码&UTF-8等的字符串长度,而须要根据编码标准自行实现。GBK 等中文编码除了计算字符串长度的函数外,字符串匹配函数也要本身实现(缘由看上文中文编码总结)。当须要支持的编码格式不断增多时,处理函数的开发和维护就须要付出更大的代价。

  方法 2 针对方法 1 的不足加以改进。在程序内部能够优先选择C标准库支持的编码方式,或者根据须要本身实现对某一特定编码格式的完整支持,这样任何编码均可以先转换为支持的编码,代码通用性比较好。

  那么C标准库对中文编码的支持如何呢?目前 Linux 平台通常使用 GNU C library,内建了对单字节的 char 和宽字符 wchar_t的支持。Char 你们都很熟悉了,处理中文须要的 wchar_t要重点介绍一下。从实现上来讲在 Linux 平台上能够认为 wchar_t是 4byte 的 int,内部存储字符的 UTF32 编码。因为标准库已经内建了对 wchar_t比较完备的支持,如使用 wcslen 计算字符串长度,使用 wcscmp 进行字符串比较等等。因此比较简单的方式是使用上面的方法2,同时选择 wchar_t做为内部字符的表示。作到这一点仍是比较容易的,在输入输出的时候经过 mbrtowc/wcrtomb 进行单个字符的内外编码转换,以及经过 mbsrtowcs/wcsrtombs 进行字符串的内外编码转换便可。这里须要注意两点:

  1. 代码中字符串常量的表示不一样。举例说明:Char c=’a’; Wchar_t wc=L’中’;
  2. 上面两组函数的转换是依赖 locale 设置的,即 locale 决定了外部编码的类型。确切的说是 LC_CTYPE 决定了外部编码的类型。默认状况下程序启动时使用标准“C”locale,而不是 LC 系列的环境变量指定的。因此须要首先调用下面的函数:setlocale (LC_ALL, “”);这样程序就使用了用户经过设置 LC 系列环境变量选择的 Locale。

  关于 locale 的话题比较大,这里就不深刻了,留待下一篇文章吧.

  上面的方法很完美,是吗?不是吗?获得这么多的好处不是无代价的,最明显的代价就是内存,任何一个字符,无论中文仍是英文若是保持在 wchar_t里就须要 4 个 byte,就这一个理由就足以限制了这个方案在关注内存使用的应用场景下的使用。

  Python 的中文处理

  对 Python 来讲因为内建 Unicode 的支持,因此采用输入输出的时候进行转换,内部保持 Unicode 的方式使用是个不错的方案。http://docs.python.org/tutorial/introduction.html#unicode-strings 这里做为起点,有兴趣的同窗自学吧。

  编码选择建议:

  1. 只有英文:绝不犹豫选择内外编码都选择 ASCII,通用且存储代价小。
  2. 主要存中文,对存储大小比较敏感:内外部编码根据文字使用范围选择 GB2312 或者 GBK,自行实现使用到的字符串处理函数。
  3. 通用性第一,处理简单:外部选择 UTF-8,内部可使用 UTF-8或者 UTF-32(即 wchar_t)

  参考资料:

  http://baike.baidu.com/view/25492.htm

  http://baike.baidu.com/view/25421.htm

  http://baike.baidu.com/view/40801.htm

  http://www.sac.gov.cn/SACSearch/search?channelid=160591&templet=gjcxjg_detail.jsp&searchword=STANDARD_CODE=’GB%202312-1980′&XZ=Q

  http://www.sac.gov.cn/SACSearch/search?channelid=160591&templet=gjcxjg_detail.jsp&searchword=STANDARD_CODE=’GB%2018030-2005′&XZ=Q

  http://www.sac.gov.cn/SACSearch/search?channelid=160591&templet=gjcxjg_detail.jsp&searchword=STANDARD_CODE=’GB%2013000-2010′&XZ=Q

  http://www.unicode.org/

  http://www.ibm.com/developerworks/cn/linux/i18n/unicode/linuni/

  http://www.gnu.org/software/libc/manual/html_node/index.html

  http://www.gnu.org/software/libiconv/

 

reference:

http://www.fmddlmyy.cn/text7.html

 http://www.cnblogs.com/MichaelOwen/articles/2128771.html

相关文章
相关标签/搜索