详谈字符编码[一]字符编码中的坑

  提及字符编码首先可能想到的词汇有Unicode,UTF,UCS,内码,区位码,跨平台等词汇,甚至还有输入法,乱码,emoji,微软雅黑,URL encode等相关的词汇也会冒出来,足以说明字符编码在计算机中重要的地位。程序员关心的是在本身的代码中如何处理好字符编码的问题,特别是像C/C++这样历史悠久而又偏向底层的语言中如何处理好这一问题。实际上这个问题在今天已经获得了很好的解决,前辈们经过努力使得今天的字符编码基本上再也不困扰程序的使用者和编程人员,但其留下的诸多历史痕迹却还一直在困扰着一代又一代的编程初学者。本系列博客共有五篇,尽可能详细但不失广度,通俗不失深度地介绍关于字符编码的内容,但求抛砖引玉。程序员

  详谈字符编码[一]将介绍编码,输入码,机内码,字形码,字形库等概念,主要说一下这些概念给咱们挖的坑。编程

  接下来的几篇还会介绍“什么是代码页”,“字符输出与消除乱码”,“宽窄字符--字符相关的数据结构”,“经常使用IO函数的效率分析”等。windows

一.编码

  编码(encode)的概念并不只仅出如今计算机科学中,字符编码(character encoding)就是要找到一种可以用表示字符的方案,以方便存储与传输。数组

  电影《无间道》中梁朝伟和黄秋生经过传输莫斯电码的方式传输信息,再或者小说里两情相悦的男女本身发明一套符号交流感情的桥段,这都是一种编码。但咱们要谈的主要仍是你们公认的字符的数字编码。这些编码像GBK,UTF-8,UCS-2,ASCII等等。还有Unicode?这就要说说这几个概念的不一样了。网络

  ASCII:信息技术萌发在西方社会,最先的编码也只针对英语的习惯(ASCII只定义了英语字母和数字,符号等)。那时候总共也不超过128个字符,编码方式一张表就全说明白了,表的内容正是字符与数字(0到127)的一一对应。在计算机上的具体实现方式就是用数字对应的二进制无符号数做为字符的机器表示数据结构

  由于那时候要编码的字符太少,人们甚至都没怎么感受到“规定字符和数字的对应方式”“规定数字的二进制表示方式”是两个相对独立的过程。多年后,面对着浩如烟海的待编码字符,这种分治成为必要。函数

  Unicode:Unicode一开始定位的问题就是给全世界的全部字符(包括古代字符)一个统一的身份证号。所谓统一就是谁跟谁也不会冲突,每一个字符都有本身的位子。相比给128个字符编码,这个问题的复杂程度前无古人。分离“制定字符集”和“制定二进制编码”这两个过程势在必行。字体

  制定统一字符集就是给全世界的字符一个编号,称为码点(code point ),通常用这样“U+7231”(表示汉字 爱 )的方式表示。码点是给人看的,机器只认识二进制,不认识什么叫”U+7241“。因此字符集定义完成也只完成了一半工做,但已经完成了主要的工做。另外一部分的工做就是把数字定义为二进制编码,这时出现了多种方案,著名的就有UTF-8,UTF-16,UTF-32,UCS-2,UCS-4。编码

  字符集说明这种表示方式支持多少字符,好比Unicode能表示不少字符(并且仍在不断扩充),ASCII就只有128个。而编码方式则用来讲明怎么用二进制表示字符集里字符对应的数字。到这里应该明白,Unicode是指一个字符集,而UTF-8,UCS-2都是具体的编码方式。ASCII呢,我想应该理解为它既说明了字符集又说明了编码方式。操作系统

  分开”定义字符集“和”定义编码“这两部分的工做,好处有不少,我大概想到三点:

  1.这两部分工做用到的领域知识并不相同,所以自然须要分开。定义字符集的专家须要至关的社会学,语言学的人文知识,而编码工做须要由有计算机底层知识的工程师去完成。

  2.定义字符集的工做能够不受编码工做的影响,有利于字符集从此的扩充。Unicode的扩充一直在进行,但编码方式基本只有UTF-8和UTF-16使用的最多,从此也基本不会再变。

  3.不一样的系统能够选择不一样的二进制实现,哪一个方便选哪一个。不一样的用途也能够选择不一样的编码,好比网络传输用UTF-8,内部存储用UTF-16。

二.UTF-8,UTF-16,UTF-32,UCS-2,UCS-4

  网上已经有很多介绍具体编码方式的文章,这里再也不赘述,只画一下重点。

  Ⅰ。UTF-16与UCS-2是不一样的编码方式(UCS-4也不一样与UTF-32)。简单的来讲,UCS是较旧的概念。早期Unicode认为32bit足以表示全世界的字符,但现实是后来发现不够便通过了一次扩充(U+XXXX变成U+XXXXXX,好比U+1F60A是emoji中的笑脸)。UCS-2只能表示扩充前的字符(请自行百度”Basic Multilingual Plane“)。UTF-16彻底兼容UCS-2,但支持全部Unicode字符,代价就是UTF-16是变长的,一个UTF-16字符多是16bit也可能用32bit(请自行百度”surrogate pair“)。

  Ⅱ。UTF-16,UTF-32存在大小端的问题,UTF-8不存在。简单来讲由于UTF-16的码元的2字节,UTF-32的码元的4字节都是多字节,而UTF-8的码元是一个字节,单字节不存在大小端问题(无所谓字节序)。

    但真正要解释清除还要说一下什么是Unicode的码元,码元(是Unicode的码元,不是通信学里说的那个码元)是机器一次处理的数据单位,是字节(8bit)的整数倍。要知道机器可不是一个字符一个字符处理的,而是一个码元一个码元的处理,具体来讲C++中UTF-8不是用char数组存储吗,因此机器一次处理一个char类型(8bit)。若是码元只包含一个字节,一次处理一个字节固然不会有大小端的问题。但若是是多字节码元(UTF-16,UTF-32)就有区别了,x86是小端模式(x64不过是x86_64),他表示的UTF-16的’爱‘固然也应该等于7231可是在内存中就是按照0x3172这样存储的,不过,也不要紧毕竟机器本身认得在wchar_t的上下文中0x3172表示的值就是0x7231。问题是一旦换一台新机器,鬼知道你的0x3172表示的是3172仍是7231?新机器也是小端那还好,默认认为0x3172就是7231。但若是是大端的机器,默认理解3172就是3172,爱就变成另外一个字了。再看单字节码元的UTF-8,不论在大端仍是小端的机器中都存储为E788B1,并且都理解为E788B1,所以也就不存在大小端问题了。

  Ⅲ。BOM(Byte Order Mark)是为了区分大小端而存在的。用U+FEFF做为第一个字符(选择它是由于U+EFFF在是非法的码点),U+FEFF这个码点的名称就是”零长不折行空格“,意思就是什么都不显示,零长度嘛,因此即使在字符中任意插入U+FEFF也不会影响显示出来的结果。一台机器读取字符时发现前两个字节是FFEF,就知道以后的文本是大端表示,若是前两个字节是EFFF,就知道以后的文本是小端表示。UTF-8没有大小端问题,BOM也就无关紧要,但若是有的话应该是什么呢?就是U+FEFF采用UTF-8编码获得的:EFBBBF。

  Ⅳ。知乎上有这样的问题:”Windows为何用GBK而不是UTF-8?“,“为何Windows简体中文默认编码是GBK?”这可真冤枉微软了,Windows会放着Unicode这么好的东西不用吗?其实Windows2000以后的操做系统内码都是UTF-16了。若是你还有有这样的误会,请关注后续博客"什么是代码页"

三.机内码,输入码,字形码

  这三个概念是应该放在一块儿的,由于分别表明了一个字符从输入处处理存储再到输出计算机的过程。输入阶段用到的是输入码,好比拼音码,五笔字型码,区位码,他们基本上都对应了一类输入法,用来完成字符的输入。当咱们使用了五笔输入并在按键上按下EP这两个按键时,一系列按键消息传到输入法程序中,输入法在把这一系列按键消息翻译成咱们想输入的汉字:爱。机内码则是字符在计算机内的表示方式,通常说的字符编码也是指机内码。字形码就有意思了。计算机知道内存中的一个字符是“爱”,要把它显示到屏幕或打印到纸上,这就须要字形码。他包含了一个字符形状的点阵信息或者矢量信息,总之是和“字符的样子”有关的信息。

                  

                     图1.1 汉字“爱”的16*16点阵

  字形的信息存储在字库中(win10中C:/windows/Fonts/目录下的就是关于字形的文件了)。好比上边的“爱”字是一个能够查询字形信息的程序输出的,这个程序就须要读取附带的字库信息。单片机想要在一个LED显示屏上显示一个字符时用到的底层驱动程序也是相似原理,针式打印机要在纸上打印一样须要字形信息。一个字固然能够有不少种字体,每一种字体都须要对应字形库的支持。多说一句字形设计但是有著做权保护的,方正楷体能够免费商用,但微软雅黑商用就须要付费了(但Windows用户能够在屏幕上显示微软雅黑,由于这部分版权费微软已经给方正付过了)。

  任何刚学完控制台输出的新手都有写一个字符画程序的冲动,这里有一个之前写的能够将图片转换为bmp位图字符画的程序例子,涉及字库的使用,bmp位图的文件格式,生成灰度图等知识。(本身吐槽:这个程序很鸡肋,要么看得清图片全貌看不清每一个汉字,要么看得清汉字但图片却超出了屏幕范围,要想写一个更好的字符画程序不得不研究一下图像处理呢)这里抖胆把效果展现一下。

       

    原图        缩小后的字符画               字符画局部,能够看到由汉字组成

 这篇的所有内容就是这样了,下一篇会介绍”宽窄字符--字符相关的数据结构“,包括C++11中新加入的char16_t和char32_t的内容。喜欢就推荐一下吧。

祝你们国庆,中秋快乐!

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息