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

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

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

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

一.编码

  编码(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的内容。喜欢就推荐一下吧。

祝大家国庆,中秋快乐!

时间: 2024-10-28 12:52:24

详谈字符编码[一]字符编码中的坑的相关文章

javascript和html中unicode编码和字符转义的详解

原文:http://ulhoo.com/blog/?p=285 1.html中的转义:在html中如果遇到转义字符(如" "),不管你的页面字符编码是utf-8亦或者是GB2312,都会直接打印成相应的字符:而当遇到(如:"\u8981"[此处的8981是16进制值])时,则不会打印成相应字符. /* *html标记的转义 *@example *<p>Hello World!</p> * ||等价于 *<p>Hello World

【整理】Python中实际上已经得到了正确的Unicode或某种编码的字符,但是看起来或打印出来却是乱码

转自:http://www.crifan.com/python_already_got_correct_encoding_string_but_seems_print_messy_code/ [背景] Python中的字符编码,其实的确有点复杂. 再加上,不同的开发环境和工具中,显示的逻辑和效果又不太相同,尤其是,中文的,初级用户,最常遇到的: (1)在Python自带的IDE:IDLE中折腾中文字符,结果看到的差不多都是乱码类的东西,比如:’\xd6\xd0\xce\xc4′ (2)将一个中文

字符、字符集、编码,以及它们python中会遇到的一些问题(上)

在看了很多的博客文章之后,总结整理得到了以下文章,非常感谢这些无私奉献的博主! 文章末尾有本文引用的文章的链接,如果有漏掉的文章引用,可以发邮件联系我,随后再次附上链接! 侵删!!! 这一部分是上篇,主要讲的是字符.字符集和字符编码的一些概念,以及他们在python中的一些简单的代码示例,偏向于概念. 下篇会说编码和解码部分,以及在python中会遇到的一些编码问题,偏向于实际应用一点. 这绝对是个源远流长的大坑,对于新手来说恶心致死(尤其是windows)........... 一.字符.字符

字符编码介绍及java中的应用

字符编码,就是对日常的控制符号.文字和常用符号的二进制表示.为了准确的表示如何编号,怎么生产八位字节流,Unicode Technical Report (UTR) #17提出现代编码模型的5个层次: 1.  抽象字符表:系统所支持的所有抽象字符的集合 2. 编码字符集:就是通过某种规则把抽象字符映射到编码空间的一个码位 3. 字符编码表:把码位转换成有限位长的整数值串,utf-8等. 4. 字符编码方案:把定长的整数转化为8bit 5. 传输编码语法:为了满足传输的需要,进一步处理字节流,ba

字符编码、字符存储、字符转换及工程中字符的使用

字符编码.字符存储.字符转换及工程中字符的使用 版本控制 版本 时间(北京时间) 作者 备注 V1.0 2016-05-13 施小丰 创建本文.第七章工程总结尚未完成 一.          前言 1.        目的 本文主要用于整理字符相关知识,包括字符编码.字符存储.行业标准.文件读写.工程注意事项等涉及字符相关的内容, 从而在实际工程中更好地设计和使用字符.更快地解决字符问题. 2.        适用范围 本文标题是"Windows C++字符编码.存储.转换大全", 但

【Java基础】Java中的char是否可以存储一个中文字符之理解字符字节以及编码集

Java中的一个char采用的是Unicode编码集,占用两个字节,而一个中文字符也是两个字节,因此Java中的char是可以表示一个中文字符的. 但是在C/C++中由于采用的字符编码集是ASCII,只有一个字节,因此是没办法表示一个中文字符的. 解答了上面的浅显易懂的问题之后,下面彻底理清楚字符 字节以及编码的原理. 其实关于编码以及字节的问题,在腾讯实习生一面的时候也问到过,当时搞不懂面试官为什么会问这个问题,现在想想,这个问题还是很考验一个人的思考以及钻研深度的,而且这个问题远远比自己想象

php页面编码与字符操作

我们可以用header来定义一个php页面为utf编码或GBK编码,也可以在html中用meta标签来指定编码 例如:php页面为utf编码    header("Content-type: text/html; charset=utf-8"); 我们通常使用header或meta,下面说一说两者的区别 一.采用meta页面编码 用meta来设置页面编码 1 <meta http-equiv="content-type" content="text/

【转载】字符,字节和编码

转自:http://www.regexlab.com/zh/encoding.htm 引言 "字符与编码"是一个被经常讨论的话题.即使这样,时常出现的乱码仍然困扰着大家.虽然我们有很多的办法可以用来消除乱码,但我们并不一定理解这些办法的内在原理.而有的乱码产生的原因,实际上由于底层代码本身有问题所导致的.因此,不仅是初学者会对字符编码感到模糊,有的底层开发人员同样对字符编码缺乏准确的理解.     1. 编码问题的由来,相关概念的理解 1.1 字符与编码的发展 从计算机对多国语言的支持

字符编码与保存编码

转载自:http://guoxinmiao8.blog.sohu.com/129816401.html 没有验证真伪 字节和字符的区别 咦,字节和字符能有什么区别啊?不都是一样的吗?完全正确,但只是在古老的DOS时代.当Unicode出现后,字节和字符就不一样了. 字节(octet)是一个八位的存储单元,取值范围一定是0-255.而字符(character,或者word)为语言意义上的符号,范围就不一定了.例如在UCS-2中定义的字符范围为0-65535,它的一个字符占用两个字节. Big En