关于计算机字符字符集集及字符字符集编码的书籍推荐

一)问题引入
1.vc项目中字符集设置的作用?(当然这个属于vc++)
2.c++中L和_T究竟代表了什么?
二)vc项目字符集设置
在我看来,字符集设置只是影响了TCHAR以及与之有关的函数。
三)汉字的编码方式及在vc/c++中的处理
1.汉字编码方式的介绍
对英文字符的处理,7位ASCII码字符集中的字符即可满足使用需求,且英文字符在计算机上的输入及输出也非常简单,因此,英文字符的输入、存储、内部处理和输出都可以只用同一个编码(如ASCII码)。
而汉字是一种象形文字,字数极多(现代汉字中仅常用字就有六、七千个,总字数高达5万个以上),且字形复杂,每一个汉字都有"音、形、义"三要素,同音字、异体字也很多,这些都给汉字的的计算机处理带来了很大的困难。要在计算机中处理汉字,必须解决以下几个问题:首先是汉字的输入,即如何把结构复杂的方块汉字输入到计算机中去,这是汉字处理的关键;其次,汉字在计算机内如何表示和存储?如何与西文兼容?最后,如何将汉字的处理结果从计算机内输出?
为此,必须将汉字代码化,即对汉字进行编码。对应于上述汉字处理过程中的输入、内部处理及输出这三个主要环节,每一个汉字的编码都包括输入码、交换码、内部码和字形码。在计算机的汉字信息处理系统中,处理汉字时要进行如下的代码转换:输入码→交换码→内部码→字形码。
(1)输入码:
作用是,利用它和现有的标准西文键盘结合来输入汉字。输入码也称为外码。主要归为四类:
数字编码:数字编码是用等长的数字串为汉字逐一编号,以这个编号作为汉字的输入码。例如,区位码、电报码等都属于数字编码。
拼音码:拼音码是以汉字的读音为基础的输入办法。
字形码:字形码是以汉字的字形结构为基础的输入编码。例如,五笔字型码(王码)。
音形码:音形码是兼顾汉字的读音和字形的输入编码。
(2)交换码:用于汉字外码和内部码的交换。交换码的国家标准代号为GB2312-80。
(3)内部码:内部码是汉字在计算机内的基本表示形式,是计算机对汉字进行识别、存储、处理和传输所用的编码。内部码也是双字节编码,将国标码两个字节的最高位都置为"1",即转换成汉字的内部码。
(4)字形码:字形码是表示汉字字形信息(汉字的结构、形状、笔划等)的编码,用来实现计算机对汉字的输出(显示、打印)。
c++ 字符集总结
做c++2年多了,一直对字符集这样概念有些模糊,却又一直都有所涉及,今天就简单的总结哈,
此问题虽简单但总结哈也能让自己更清晰。
1.多字节字符集和Unicode字符集区别
多字节字符集英文...
C++11 之字符集
字符集问题1.基础概念理解 1)通常我们将一个标准中能够表示所有字符的一个集合称为字符集,如Unicode字符集。 2)在字符集中每个字符占用一个唯一的码位。如Unicode就有1114112个字符码...
Visual Studio中C++关于Unicode字符集和多字节字符集
1.Unicode字符集
原本标准字符集为8位的ASCII码,但世界上的书写语言不能简单地用256个8位代码即一字节表示,就试更宽的值,例如16位值。这就是Unicode非常简单的原理。与混乱...
c++ 字符集
【问题来源: 使用qt查找html文件中的>>(\u00BB)符号时总是失败。 代码示例 find(&>>&,context) 其中context为QString类型,由文件输入得到。】
一般来说,c...
C++: 对字符串转换字符集(编码)
最近,linux上遇到string为汉字时,需要转码存入到数据库中,将转码的函数及其方法记录一下。
头文件是#include
//fromcode:源string使用的字符...
C++的字符集
字符集是组成程序设计语言的
C++字符集问题终极分析(可解决乱码问题)
最近研究vc,windows的东西真是很傻瓜,啥都给你做好,有个好处就是开发方便了。
有个弊端就是完全按微软的一套进行,规则都是它定的,你得知道它的很多api,
开发出来的代码效率不高,不过却可以...
C++字符集和词法记号
C++字符集和词法记号
大小写的英文字母:A~Z,a~z
数字字符:0~9
特殊字符:
ASCII字符集
ASCII字符集
摘自 书上内容
计算机使用数字代码来存储字符。ASCII码是美国最常用的编码,它是Unicode的一个子集(一个非常小的子集)。C++使得能够直接表示大多数字符,方法...
没有更多推荐了,关于字符编码,你所需要知道的 - 文章 - 伯乐在线
& 关于字符编码,你所需要知道的
字符编码的问题看似很小,经常被技术人员忽视,但是很容易导致一些莫名其妙的问题。这里总结了一下字符编码的一些普及性的知识,希望对大家有所帮助。
还是得从ASCII码说起
说到字符编码,不得不说ASCII码的简史。计算机一开始发明的时候是用来解决数字计算的问题,后来人们发现,计算机还可以做更多的事,例如文本处理。但由于计算机只识“数”,因此人们必须告诉计算机哪个数字来代表哪个特定字符,例如65代表字母‘A’,66代表字母‘B’,以此类推。但是计算机之间字符-数字的对应关系必须得一致,否则就会造成同一段数字在不同计算机上显示出来的字符不一样。因此美国国家标准协会ANSI制定了一个标准,规定了常用字符的集合以及每个字符对应的编号,这就是ASCII字符集(Character Set),也称ASCII码。
当时的计算机普遍使用8比特字节作为最小的存储和处理单元,加之当时用到的字符也很少,26个大小写英文字母还有数字再加上其他常用符号,也不到100个,因此使用7个比特位就可以高效的存储和处理ASCII码,剩下最高位1比特被用作一些通讯系统的奇偶校验。
注意,字节代表系统能够处理的最小单位,不一定是8比特。只是现代计算机的事实标准就是用8比特来代表一个字节。在很多技术规格文献中,为了避免产生歧义,更倾向于使用8位组(Octet)而不是字节(Byte)这个术语来强调8个比特的二进制流。下文中为了便于理解,我会延用大家熟悉的“字节”这个概念。
ASCII字符集由95个可打印字符(0x20-0x7E)和33个控制字符(0x00-0x19,0x7F)组成。可打印字符用于显示在输出设备上,例如荧屏或者打印纸上,控制字符用于向计算机发出一些特殊指令,例如0x07会让计算机发出哔的一声,0x00通常用于指示字符串的结束,0x0D和0x0A用于指示打印机的打印针头退到行首(回车)并移到下一行(换行)。
那时候的字符编解码系统非常简单,就是简单的查表过程。例如将字符序列编码为二进制流写入存储设备,只需要在ASCII字符集中依次找到字符对应的字节,然后直接将该字节写入存储设备即可。解码二进制流的过程也是类似。
OEM字符集的衍生
当计算机开始发展起来的时候,人们逐渐发现,ASCII字符集里那可怜的128个字符已经不能再满足他们的需求了。人们就在想,一个字节能够表示的数字(编号)有256个,而ASCII字符只用到了0x00~0x7F,也就是占用了前128个,后面128个数字不用白不用,因此很多人打起了后面这128个数字的主意。可是问题在于,很多人同时有这样的想法,但是大家对于0x80-0xFF这后面的128个数字分别对应什么样的字符,却有各自的想法。这就导致了当时销往世界各地的机器上出现了大量各式各样的OEM字符集。
下面这张表是IBM-PC机推出的其中一个OEM字符集,字符集的前128个字符和ASCII字符集的基本一致(为什么说基本一致呢,是因为前32个控制字符在某些情况下会被IBM-PC机当作可打印字符解释),后面128个字符空间加入了一些欧洲国家用到的重音字符,以及一些用于画线条画的字符。
事实上,大部分OEM字符集是兼容ASCII字符集的,也就是说,大家对于0x00~0x7F这个范围的解释基本是相同的,而对于后半部分0x80~0xFF的解释却不一定相同。甚至有时候同样的字符在不同OEM字符集中对应的字节也是不同的。
不同的OEM字符集导致人们无法跨机器交流各种文档。例如职员甲发了一封简历résumés给职员乙,结果职员乙看到的却是rsums,因为é字符在职员甲机器上的OEM字符集中对应的字节是0x82,而在职员乙的机器上,由于使用的OEM字符集不同,对0x82字节解码后得到的字符却是。
多字节字符集(MBCS)和中文字符集
上面我们提到的字符集都是基于单字节编码,也就是说,一个字节翻译成一个字符。这对于拉丁语系国家来说可能没有什么问题,因为他们通过扩展第8个比特,就可以得到256个字符了,足够用了。但是对于亚洲国家来说,256个字符是远远不够用的。因此这些国家的人为了用上电脑,又要保持和ASCII字符集的兼容,就发明了多字节编码方式,相应的字符集就称为多字节字符集。例如中国使用的就是双字节字符集编码(DBCS,Double Byte Character Set)。
对于单字节字符集来说,代码页中只需要有一张码表即可,上面记录着256个数字代表的字符。程序只需要做简单的查表操作就可以完成编解码的过程。
代码页是字符集编码的具体实现,你可以把他理解为一张“字符-字节”映射表,通过查表实现“字符-字节”的翻译。下面会有更详细的描述。
而对于多字节字符集,代码页中通常会有很多码表。那么程序怎么知道该使用哪张码表去解码二进制流呢?答案是,根据第一个字节来选择不同的码表进行解析。
例如目前最常用的中文字符集GB2312,涵盖了所有简体字符以及一部分其他字符;GBK(K代表扩展的意思)则在GB2312的基础上加入了对繁体字符等其他非简体字符(GB18030字符集不是双字节字符集,我们在讲Unicode的时候会提到)。这两个字符集的字符都是使用1-2个字节来表示。Windows系统采用936代码页来实现对GBK字符集的编解码。在解析字节流的时候,如果遇到字节的最高位是0的话,那么就使用936代码页中的第1张码表进行解码,这就和单字节字符集的编解码方式一致了。
当字节的高位是1的时候,确切的说,当第一个字节位于0x81–0xFE之间时,根据第一个字节不同找到代码页中的相应的码表,例如当第一个字节是0x81,那么对应936中的下面这张码表:
(关于936代码页中完整的码表信息,参见MSDN:.)
按照936代码页的码表,当程序遇到连续字节流0x81 0x40的时候,就会解码为“丂”字符。
ANSI标准、国家标准、ISO标准
不同ASCII衍生字符集的出现,让文档交流变得非常困难,因此各种组织都陆续进行了标准化流程。例如美国ANSI组织制定了ANSI标准字符编码(注意,我们现在通常说到ANSI编码,通常指的是平台的默认编码,例如英文操作系统中是ISO-8859-1,中文系统是GBK),ISO组织制定的各种ISO标准字符编码,还有各国也会制定一些国家标准字符集,例如中国的GBK,GB2312和GB18030。
操作系统在发布的时候,通常会往机器里预装这些标准的字符集还有平台专用的字符集,这样只要你的文档是使用标准字符集编写的,通用性就比较高了。例如你用GB2312字符集编写的文档,在中国大陆内的任何机器上都能正确显示。同时,我们也可以在一台机器上阅读多个国家不同语言的文档了,前提是本机必须安装该文档使用的字符集。
Unicode的出现
虽然通过使用不同字符集,我们可以在一台机器上查阅不同语言的文档,但是我们仍然无法解决一个问题:在一份文档中显示所有字符。为了解决这个问题,我们需要一个全人类达成共识的巨大的字符集,这就是Unicode字符集。
Unicode字符集概述
Unicode字符集涵盖了目前人类使用的所有字符,并为每个字符进行统一编号,分配唯一的字符码(Code Point)。Unicode字符集将所有字符按照使用上的频繁度划分为17个层面(Plane),每个层面上有216=65536个字符码空间。
其中第0个层面BMP,基本涵盖了当今世界用到的所有字符。其他的层面要么是用来表示一些远古时期的文字,要么是留作扩展。我们平常用到的Unicode字符,一般都是位于BMP层面上的。目前Unicode字符集中尚有大量字符空间未使用。
编码系统的变化
在Unicode出现之前,所有的字符集都是和具体编码方案绑定在一起的,都是直接将字符和最终字节流绑定死了,例如ASCII编码系统规定使用7比特来编码ASCII字符集;GB2312以及GBK字符集,限定了使用最多2个字节来编码所有字符,并且规定了字节序。这样的编码系统通常用简单的查表,也就是通过代码页就可以直接将字符映射为存储设备上的字节流了。例如下面这个例子:
这种方式的缺点在于,字符和字节流之间耦合得太紧密了,从而限定了字符集的扩展能力。假设以后火星人入住地球了,要往现有字符集中加入火星文就变得很难甚至不可能了,而且很容易破坏现有的编码规则。
因此Unicode在设计上考虑到了这一点,将字符集和字符编码方案分离开。
也就是说,虽然每个字符在Unicode字符集中都能找到唯一确定的编号(字符码,又称Unicode码),但是决定最终字节流的却是具体的字符编码。例如同样是对Unicode字符“A”进行编码,UTF-8字符编码得到的字节流是0x41,而UTF-16(大端模式)得到的是0x00 0x41。
常见的Unicode编码
UCS-2/UTF-16
如果要我们来实现Unicode字符集中BMP字符的编码方案,我们会怎么实现?由于BMP层面上有216=65536个字符码,因此我们只需要两个字节就可以完全表示这所有的字符了。
举个例子,“中”的Unicode字符码是0x4E2D(01101),那么我们可以编码为01101(大端)或者01110 (小端)。
UCS-2和UTF-16对于BMP层面的字符均是使用2个字节来表示,并且编码得到的结果完全一致。不同之处在于,UCS-2最初设计的时候只考虑到BMP字符,因此使用固定2个字节长度,也就是说,他无法表示Unicode其他层面上的字符,而UTF-16为了解除这个限制,支持Unicode全字符集的编解码,采用了变长编码,最少使用2个字节,如果要编码BMP以外的字符,则需要4个字节结对,这里就不讨论那么远,有兴趣可以参考维基百科:。
Windows从NT时代开始就采用了UTF-16编码,很多流行的编程平台,例如.Net,Java,Qt还有Mac下的Cocoa等都是使用UTF-16作为基础的字符编码。例如代码中的字符串,在内存中相应的字节流就是用UTF-16编码过的。
UTF-8应该是目前应用最广泛的一种Unicode编码方案。由于UCS-2/UTF-16对于ASCII字符使用两个字节进行编码,存储和处理效率相对低下,并且由于ASCII字符经过UTF-16编码后得到的两个字节,高字节始终是0x00,很多C语言的函数都将此字节视为字符串末尾从而导致无法正确解析文本。因此一开始推出的时候遭到很多西方国家的抵触,大大影响了Unicode的推行。后来聪明的人们发明了UTF-8编码,解决了这个问题。
UTF-8编码方案采用1-4个字节来编码字符,方法其实也非常简单。
(上图中的x代表Unicode码的低8位,y代表高8位)
对于ASCII字符的编码使用单字节,和ASCII编码一摸一样,这样所有原先使用ASCII编解码的文档就可以直接转到UTF-8编码了。对于其他字符,则使用2-4个字节来表示,其中,首字节前置1的数目代表正确解析所需要的字节数,剩余字节的高2位始终是10。例如首字节是1110yyyy,前置有3个1,说明正确解析总共需要3个字节,需要和后面2个以10开头的字节结合才能正确解析得到字符。
关于UTF-8的更多信息,参考维基百科:。
任何能够将Unicode字符映射为字节流的编码都属于Unicode编码。中国的GB18030编码,覆盖了Unicode所有的字符,因此也算是一种Unicode编码。只不过他的编码方式并不像UTF-8或者UTF-16一样,将Unicode字符的编号通过一定的规则进行转换,而只能通过查表的手段进行编码。
关于GB18030的更多信息,参考:。
Unicode相关的常见问题
Unicode是两个字节吗?
Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储为什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-16和UTF-8。
带签名的UTF-8指的是什么意思?
带签名指的是字节流以BOM标记开始。很多软件会“智能”的探测当前字节流使用的字符编码,这种探测过程出于效率考虑,通常会提取字节流前面若干个字节,看看是否符合某些常见字符编码的编码规则。由于UTF-8和ASCII编码对于纯英文的编码是一样的,无法区分开来,因此通过在字节流最前面添加BOM标记可以告诉软件,当前使用的是Unicode编码,判别成功率就十分准确了。但是需要注意,不是所有软件或者程序都能正确处理BOM标记,例如PHP就不会检测BOM标记,直接把它当普通字节流解析了。因此如果你的PHP文件是采用带BOM标记的UTF-8进行编码的,那么有可能会出现问题。
Unicode编码和以前的字符集编码有什么区别?
早期字符编码、字符集和代码页等概念都是表达同一个意思。例如GB2312字符集、GB2312编码,936代码页,实际上说的是同个东西。但是对于Unicode则不同,Unicode字符集只是定义了字符的集合和唯一编号,Unicode编码,则是对UTF-8、UCS-2/UTF-16等具体编码方案的统称而已,并不是具体的编码方案。所以当需要用到字符编码的时候,你可以写gb2312,codepage936,utf-8,utf-16,但请不要写unicode(看过别人在网页的meta标签里头写charset=unicode,有感而发)。
乱码指的是程序显示出来的字符文本无法用任何语言去解读。一般情况下会包含大量?或者?。乱码问题是所有计算机用户或多或少会遇到的问题。造成乱码的原因就是因为使用了错误的字符编码去解码字节流,因此当我们在思考任何跟文本显示有关的问题时,请时刻保持清醒:当前使用的字符编码是什么。只有这样,我们才能正确分析和处理乱码问题。
例如最常见的网页乱码问题。如果你是网站技术人员,遇到这样的问题,需要检查以下原因:
服务器返回的响应头Content-Type没有指明字符编码
网页内是否使用META HTTP-EQUIV标签指定了字符编码
网页文件本身存储时使用的字符编码和网页声明的字符编码是否一致
注意,网页解析的过程如果使用的字符编码不正确,还可能会导致脚本或者样式表出错。具体细节可以参考我以前写过的文章:和。
不久前看到某技术论坛有人反馈,WinForm程序使用Clipboard类的GetData方法去访问剪切板中的HTML内容时会出现乱码的问题,我估计也是由于WinForm在获取HTML文本的时候没有用对正确的字符编码导致的。Windows剪贴板只支持UTF-8编码,也就是说你传入的文本都会被UTF-8编解码。这样一来,只要两个程序都是调用Windows剪切板API编程的话,那么复制粘贴的过程中不会出现乱码。除非一方在获取到剪贴板数据之后使用了错误的字符编码进行解码,才会得到乱码(我做了简单的WinForm剪切板编程实验,发现GetData使用的是系统默认编码,而不是UTF-8编码)。
关于乱码中出现?或者?,这里需要额外提一下,当程序使用特定字符编码解析字节流的时候,一旦遇到无法解析的字节流时,就会用?或者?来替代。因此,一旦你最终解析得到的文本包含这样的字符,而你又无法得到原始字节流的时候,说明正确的信息已经彻底丢失了,尝试任何字符编码都无法从这样的字符文本中还原出正确的信息来。
必要的术语解释
字符集(Character Set),字面上的理解就是字符的集合,例如ASCII字符集,定义了128个字符;GB2312定义了7445个字符。而计算机系统中提到的字符集准确来说,指的是已编号的字符的有序集合(不一定是连续)。
字符码(Code Point)指的就是字符集中每个字符的数字编号。例如ASCII字符集用0-127这连续的128个数字分别表示128个字符;GBK字符集使用区位码的方式为每个字符编号,首先定义一个94X94的矩阵,行称为“区”,列称为“位”,然后将所有国标汉字放入矩阵当中,这样每个汉字就可以用唯一的“区位”码来标识了。例如“中”字被放到54区第48位,因此字符码就是5448。而Unicode中将字符集按照一定的类别划分到0~16这17个层面(Planes)中,每个层面中拥有216=65536个字符码,因此Unicode总共拥有的字符码,也即是Unicode的字符空间总共有17*2。
编码的过程是将字符转换成字节流。
解码的过程是将字节流解析为字符。
字符编码(Character Encoding)是将字符集中的字符码映射为字节流的一种具体实现方案。例如ASCII字符编码规定使用单字节中低位的7个比特去编码所有的字符。例如‘A’的编号是65,用单字节表示就是0x41,因此写入存储设备的时候就是b’’。GBK编码则是将区位码(GBK的字符码)中的区码和位码的分别加上0xA0(160)的偏移(之所以要加上这样的偏移,主要是为了和ASCII码兼容),例如刚刚提到的“中”字,区位码是5448,十六进制是0x3630,区码和位码分别加上0xA0的偏移之后就得到0xD6D0,这就是“中”字的GBK编码结果。
代码页(Code Page)一种字符编码具体形式。早期字符相对少,因此通常会使用类似表格的形式将字符直接映射为字节流,然后通过查表的方式来实现字符的编解码。现代操作系统沿用了这种方式。例如Windows使用936代码页、Mac系统使用EUC-CN代码页实现GBK字符集的编码,名字虽然不一样,但对于同一汉字的编码肯定是一样的。
大小端的说法源自《格列佛游记》。我们知道,鸡蛋通常一端大一端小,小人国的人们对于剥蛋壳时应从哪一端开始剥起有着不一样的看法。同样,计算机界对于传输多字节字(由多个字节来共同表示一个数据类型)时,是先传高位字节(大端)还是先传低位字节(小端)也有着不一样的看法,这就是计算机里头大小端模式的由来了。无论是写文件还是网络传输,实际上都是往流设备进行写操作的过程,而且这个写操作是从流的低地址向高地址开始写(这很符合人的习惯),对于多字节字来说,如果先写入高位字节,则称作大端模式。反之则称作小端模式。也就是说,大端模式下,字节序和流设备的地址顺序是相反的,而小端模式则是相同的。一般网络协议都采用大端模式进行传输。
参考链接:注:由于两边同步的麻烦,更多更改及调整可参考我的网站:上的,已将字符集编码系列与乱码探源系列合并,更新及勘误等不再更新到这边。
?,首先,这并不是图片,这是一个unicode字符,Yin Yang,即阴阳符,码点为U+262F。如果你的浏览器无法显示,可以查看这个链接。这与我们要讨论的主题有何关系呢?下面我会谈到。
连续式表示带来的分隔难题
计算机的底层表示
在计算机的最底层,一切都成了0和1,你也许见过一些极客(Geek)穿着印有一串0和1的衣服招摇过市,像是数字化时代的某种图腾。比如,这么一串“1000…”,如果它来自某个文本文件保存后的结果,我们如何从这一串的0和1中从新解码得到一个个的字符呢?显然你需要把这一串的0和1分成一段一段的0和1,在讲述编码是如何分隔之前,我们先看看自然语言的分隔问题。
自然语言的分隔问题
大家是否意识到,我们的中文句子里字词之间也是连续的呢?英文里说“hello world”,我们说“你好世界”,“我们 不 需要 在 中间 加 空格!”在古代,句与句之间甚至都没有分开,那时还没有标点!所以有了所谓的断句问题。让我们来看一个例子:
民可使由之不可使知之 ——出自《论语 第八章 泰伯篇》
这么一串十个字要如何去分隔并解释呢?
民可使由之,不可使知之。
解释:你可以去驱使你的民众,但不可让他们知道为什么(不要去教他们知识)
评论:很显然是站在统治阶级立场的一种愚民论调。
民可,使由之;不可,使知之。
解释:民众可以做的,放手让他们去做;不会做的,教会他们如何去做(又或:不可以去做的,让他们明白为何不可以)
评论:这看来是种不错的主张。
显然,以上的文字是以某种定长或变长的方式组合在一起的,但是关于它们如何分隔的信息则被丢弃了,于是在解释时就存在产生歧义可能了。
如何快速准确分词在中文NLP领域还是蛮有挑战的一件事,当然了,字符集编码的分隔就简单多了。
编码的分隔
自然语言中我们可以使用空格,标点来减少歧义的发生。在计算机里,一切都数字化了,包括所谓的空格,标点之类的分隔符。
老子说“道生一,一生二,二生三,三生万物”,计算机则是“二生万物”,0和1表示了一切。
在空格与标点都被数字化的情况下,我们在这一串01中如何去找出分隔来呢?显然我们需要外部的约定。
8位(bit)一组的字节是最基本的一个约定,也是文件的基本单位,文件就是字节的序列。字节显然就是最基础的一个分隔依据。
定长(Fixed-length)的解决方案
定长仅表明段与段之间长度相同,但没说明是多长。有了字节这一基本单位,我们就可以说得更具体,如定长一字节或者定长二字节。
ASCII编码是最早也是最简单的一种字符编码方案,使用定长一字节来表示一个字符。
下面我们来看一个具体的编码示例,为了方便,采用了十进制,大家看起来也更直观,原理与二进制是一样的。
假设我们现在有个文件,内容是,假如定长2位(这里的位指十进制的位)是唯一的编码方案,用它去解码,就会得到“hhhe”(可以对比图上的编码,00代表h,所以前6个0转化成3个h,后面的01则转化成e)。
但是,如果定长2位不是唯一的编码方案呢?如上图中的定长4位方案,如果我们误用定长4位去解码,结果就只能得到“he”(0000转化为h,0001转化为e)!
毕竟,文件的内容并没有暗示它使用了何种编码!这就好比孔夫子写下“民可使由之不可使知之”时并没有暗示它是5|5分隔(民可使由之|不可使知之)还是2|3|2|3分隔(民可|使由之|不可|使知之)那样。
如何区分不同的定长(以及变长)编码方式?
答案是:你无法区分!好吧,这么说可能有点武断,有人可能会说BOM(Byte Order Mark 字节顺序标识)能否算作某种区分手段呢?但也有很多情况是没有BOM的。
关于BOM,可见
总之,我想给读者传递的一个信息就是
文本文件作为一种通用的文件,在存储时一般都不会带上其所使用编码的信息。编码信息与文件内容的分离,其实这正是乱码的根源。
我们说无法区分即是基于这一点而言,但另一方面,各种编码方案所形成的字节序列也往往带有某种特征,综合统计学,语言偏好等因素,还是有可能猜测出正确的编码的,比如很多浏览器中都有所谓“编码自动检测”的功能。
本章主题主讲定变长,更多讨论可见。
定长多字节方案是如何来的?
显然,字符集的扩充是主要推动力。定长一字节编码空间撑死了也就28=256。
这点可怜的空间拿到中国来,它能编码的汉字量也就小学一年级的水平。
其实变长多字节方案更早出现,比如GB2312,采用变长主要为了兼容一字节的ASCII,汉字则用两字节表示(这也是迫不得已的事,一字节压根不够用)。随着计算机在全世界的推广,各种编码方案都出来了,彼此之间的转换也带来了诸多的问题。采用某种统一的标准就势在必行了,于是乎天上一声霹雳,Unicode粉墨登场!
前面已经谈到,Unicode早期是作为定长二字节方案出来的。它的理论编码空间一下达到了216 =65536(即64K,这里1K=)。
对于只用到ASCII字符的人来说,比如老美,让他们采用Unicode,多少还是有些怨言的。怎么说呢?比如“he”两个字符,用ASCII只需要保存成6865(16进制),现在则成了,前面两个毫无作用的0怎么看怎么碍眼,原来假设是1KB的文本文件现在硬生生就要变成2KB,1GB的则变成2GB!
可是更糟糕的事还在后头,在老美眼中,16位的空间已经算是天量了!要知道一字节里ASCII也仅仅用了一半,
后面将会看到,这一特性为各种变长方案能兼容它提供了很大便利!因为最高位都是0.
而且这一半里还有不少控制符。可随着整理工作的深入,人们发现,16位空间还是不够!!
说起来我们的中文可是字符集里面的大头。“茴字有四种写法”,上大人孔乙己的这句名言想必大家还有点印象。据说有些新近整理的汉语字典收录的汉字数量已经高达10万级别!我的天!这里很多字怕是孔乙己先生也未必认得了!
那现在该咋办呢?如果还是定长的方案,眼瞅着就要奔着四字节而去了。
计算机界有动不动就翻倍的优良传统,比如从16位机一下就到32位,32位一下又到了64位。当然了,这里面是有各种权衡的,包括硬件方面的。
那些看到把6865保存成已经很不爽的人,现在你却对他们说,“嘿,伙计,可能你需要进一步存成0065…”。容量与效率的矛盾在这时候开始激化。
容量与效率的矛盾
首先,需要明确一下:
所谓容量,这里指用几个字节表示一个字符,显然用的字节越多,编码空间越大,能表示更多不同的字符,也即容量越大。
所谓效率,当表示一个字符用的字节越多,所占用的存储空间也就越大,换句话说,存储(乃至检索)的效率降低了。
如果说效率是阴,那么容量就是阳。(我没还没忘记自小学语文老师就开始教导的,写作文要遥相呼应)
我们说定长不是问题,关键是定几位。定少了不够用,定多了太浪费。定得恰到好处?可怎样才算恰好呢?你可能会说,至少要能容纳所有字符吧?但重要的事实是并非所有的字符所有的人都用得上!哪怕用得上,也可能是偶尔用上,多数时候还是用不上!
字符之间并不是平等的。用数学的语言来说,每个字符出现的机率不是等概率的,但表示它们却用了同样长度的字节。
学过《数据结构与算法》的同学可能听过哈夫曼编码(Huffman Coding),又称霍夫曼编码,就为了解决这样的问题。
如果你对前一篇所发的莫尔斯电码图还有印象,你就会发现,字母e只用了一个点(dot)来编码。
其它字母可能觉得不公平,为啥我们就要录入那么多个点和划(dash)才行呢?这里面其实是有统计规律支撑的。e出现的概率是最大的。z你能想到什么?
zoo大概很多人能想到,厉害一点可能还能想到zebra(斑马),Zuckerberg(扎克伯格),别翻字典!你还能想到更多不?
但含有的e的单词则多了去了。zebra中不就有个e吗,Zuckerberg中还两个e呢!
在存储图片时,一个像素点用几个位来表示也是一个很值得考究的问题。你也许听过所谓的24位真彩色,这暗示了一个像素使用了高达3个字节来表示。24位的空间可以表示高达1600多万种颜色,但各种颜色的出现概率在均衡度上肯定要好于字符。
回到我们的主题,虽然很多字连我们的孔乙己先生见了都要摇头,可还是有少部分人会用到它们,比如一些研究古汉字的学者。有些人取名还喜欢弄些偏僻字,所以很多人口登记方面的系统也有这个超大字符集的需求。
好吧,我们不能不顾这些人需求。那么有可能在定长方案的框架下解决这一容量与效率的矛盾吗?答案是否定的!
矛盾是事物发展的动力,下面我们将看到定长方案的简单性使它无法缓和容量与效率的冲突,平衡这一对矛盾的努力最终推动了编码方案从定长演变到变长,事情也由此从简单变得复杂了。
CAP理论及扩展
CAP是什么玩意?
著名的Brewer猜想说:对于现代分布式应用系统来说,数据一致性(Consistency)、系统可用性(Availability)、服务规模可分区性(Partitioning)三个目标(合称CAP)不可同时满足,最多只能选择两个。
你可能要问,这貌似跟我们要讨论的问题风牛马不相及?别着急,我们可以借鉴他的这种思想,扩展到我们的问题上来。
我们所应对的问题常常很抽象,有时借助某些隐喻(metaphor)可以帮助我们来理解。天平就是一个很好的隐喻。
先看图中四个天平。你叫它跷跷板我也没意见,反正我没打算吃美术这碗饭!(嗯,也许是少个了指针的缘故,希望这空指针的天平不要引发什么异常。)
这幅图表示的就是定长方案下容量与效率的一种约束关系。
容量小则效率高
容量大则效率低
容量与效率均不能让人满意!
容量与效率均较好,但这是不可能的情形!
让我们具体解释一下:
天平的蓝色横梁一种刚性约束的隐喻。所谓刚性,这里简单理解成不能变形就是了。
天平的两端的容量与效率是它的两个维度,或者说两个自由度。不必去纠结物理学上的定义,简单理解就是它们能自由上下就好了。但我们可以看到,由于受到横梁的约束,两个维度同时向上是不可能的!它们的相互运动呈现出彼消此长的关系。
天平可以隐喻很多,比如安全性与便利性。为什么要我录入验证码?为什么支付宝登录与支付要用不一样的密码?为何输入密码还不够,还要什么手机验证码?这些都很不方便呀!当你觉得只有一个前门还不够方便时你又加开了个后门,但你是否想过在方便自己的同时也“方便”了小偷呢?鱼和熊掌不可兼得!
好了,现在要再次拓展一个维度了,以使得它更像CAP理论。
还有一个维度在哪呢?我们说容量大是好,效率高是好,我们为何青睐定长方案呢?因为定长它简单,简单当然也是好,复杂就不好了。这就是第三个维度——简单性(你也可以叫成复杂性,意思是一样的)。
先深呼吸一下,让我们再看一个图:
首先这里多了一个维度,天平模型不足以表达了,改用三条互成120度的直线表示这三个维度。越往里,红色的字代表是越差的情况;越往外,绿色的字代表是越好的情况。
图中的约束在哪呢?就在蓝色的三角形上,它有一个固定的周长,这就代表了它的约束。也许我们把它想像成一条首尾相接的固定长度的钢丝绳更好,在图中它只是被拉成了三角形。它可以在三个维度上运动,这让它比天平的横梁更灵活,但它的长度不能被拉伸,它不是橡皮绳!!
这幅图能告诉我们什么?
图1跟图2,当我们维持简单性不变时,容量与效率的关系其实跟天平模型中是相似的,也是一种彼消此长的关系。
图3,让简单性下降(换句话说就是变复杂了),才能为其它两个维度腾出“余量”来。即是说你要“牺牲”简单性来调和容量与效率的矛盾。
我们能从模型得到什么启示呢?
一、事物的多个方面往往是相互制衡的
在前面的图中,我们用钢丝绳来形象隐喻这种制衡,深刻理解制衡是各种直觉与预见性的前提,当我们作出决定时,便能够预判出可能的后果。深层次的矛盾暗示了我们有些冲突是不可避免的,同时为我们找到正确解决问题的路径指明了方向。
二、制衡的局面暗示了凡事有代价,站在一个全局上去考量,我们常常需要在各方面达成某种平衡与妥协。
举个例子,分层会对性能有所损害,但不分层又会带来紧耦合的问题。很多时候,架构就是关于平衡的艺术。如果我们能明白这一点,就不会为无法找到“完美”方案而苦恼。
三、复杂性从根本上是由需求所决定的。我们既要求容量大,又要求效率高,这种要求本身就不简单,因此也无法简单地解决。
你功能做好了,用户说性能还不行;你性能达标了,用户说界面还太丑。。。丫的能不能先把首付款付清了再跟哥提要求?!
为什么谈这些理论?
一方面我不想仅仅为谈定变长而只谈定变长,单纯谈理论又往往过于抽象。我想说的一点是“我们在编码上所遇到的困境往往不过是我们在软件开发过程中遇到的各种困境的一个缩影”。
下图是所谓的芒德布罗集(Mandelbrot set),是分形(Fractal)理论中的一个重要概念。图片来自wiki百科。
很多问题需要我们站在更高层面上去观察与思考时才能发现它们的某些相似性或者说共性,这些共性被抽象出来也就形成了我们的理论。
不识庐山真面目,只缘身在此山中。——苏轼《题西林壁》
另一方面,因为这种相似性,我们在编码问题上得到启示也能够指导我们去解决其它领域的问题。我想这就是这些理论的意义所在。
调和矛盾的努力,兼容考虑与变长方案的引入
通过前面分析,我们知道,定长二字节方案无法满足容量增长,转向定长四字节又会引发了效率危机,最终,Unicode编码方案演化成了变长的UTF-16编码方案。那么UTF-8方案又是如何来的呢?为何不能统一成一个方案呢?搞这么多学起来真头痛!
我们前面提到了有一群ASCII死忠对Unicode统一使用二字节编码ASCII字符始终是有不满的,现在眼见简单的定长方案也不行了,他们中的一些大牛终于忍无可忍。既然决定抛弃定长,他们决定变得更彻底,于是这帮人揭竿而起,捣鼓出了能与ASCII兼容的UTF-8方案。(注:真实历史也许并非如此,我不是考据癖,以上叙述大家悠着看就是了,别太当真。)
如今装个逼还分高低格,大牛不折腾,谁又知道他们是大牛呢?只是可怜了我们这些码农,你还在苦苦研究sql,忽如一夜春风来,Nosql菊花朵朵开。(貌似应开在秋冬季?)
UTF-16用所谓的代理对(surrogate pair)来编码U+FFFF以上的字符。在采用了变长之后,事情变得复杂了,以后我们还将继续探讨代理区,代理对,编码单元(Code Unit)等一系列由此而来的概念。
这种变化甚至还影响到对字符串长度的定义,比如,在java中,你可能认为包含一个字符的字符串它的长度就是1,但现在,一个字符它的长度也可能是2!这样的字符也无法用char来存储了。
UTF-8因为能兼容ASCII而受到广泛欢迎,但在保存中文方面,要用3个字节,有的甚至要4个字节,所以在保存中文方面效率并不算太好,与此相对,GB2312,GBK之类用两字节保存中文字符效率上会高,同时它们也都兼容ASCII,所以在中英混合的情况下还是比UTF-8要好,但在国际化方面及可扩展空间上则不如UTF-8了。
其实GBK之后又还有GB18030标准,采用了1,2,4字节变长方案,把Unicode字符也收录了进来。GB18030其实是国家强制性标准,但感觉推广并不是很给力。
目前,UTF-8方案在越来越多的地方有成为一种默认的编码选择的趋势。下面是一张来自wiki百科的图片,反映了UTF-8在web上的增长。
在软件开发的各个环节强制统一采用UTF-8编码,依旧是避免乱码问题的最有效措施,没有之一。技术人员也许更偏爱技术问题技术解决,但不得不承认有时行政手段更加高效!
变长(Variable-length)的编码方案
好了,现在是时候谈谈变长方案的实现问题了,在这里将通过尝试自己设计来探讨这一问题。
变长设计的核心问题自然就是如何区分不同的变长字节,只有这样才能在解码时不发生歧义。
利用高位作区分
还是以前面的例子来看,我们设计了几种变长方案
第一种方案的想法很美好,它试图跟随编号来自然增长,它还是可以编码的,但在解码时则遇到了困难。让我们来看看。
可见,由于低位的码位被“榨干”了,导致单个位与多位间无法区分,所以这种方案是行不通的。
第二种方案,低位空间有所保留,5及以上的就不使用了。然后我们通过引入一条变长解码规则:
从左向右扫描,读到5以下数字按单个位解码;读到5或以上数字时,把当前数字及下一个数字两位一起读上来解码。
让我们来看个实例
这种方案避免了歧义,因此是可行的方案,但这还是非常粗糙的设计,如果我们想在这串字符中搜索“o”这个字符,它的编码是3,这样在匹配时也会匹配上53中的3,这种设计会让我们在实现匹配算法时困难重重。我们可以在跟随位上也完全舍弃低位的编码,比如以55,56,57,58,59,65,66…这样的形式,但这样也会损失更多的有效编码位。
GB2312,GBK,UTF-8的基本思想也是如此。下面也简单示例一下(0,1代表固定值;黑色的X代表可以为0或1,为有效编码位):
注:GBK第二字节最高位也可能为0.
其实关键就在于用高位保留位来做区分,缺点就是有效编码空间少了,可以看到三字节的UTF-8方式中实际有效的编码空间只剩两字节。但这是变长方案无法避免的。
我们还可以看到,由于最高位不同,多字节中不会包含一字节的模式。对于UTF-8而言,二字节的模式也不会包含在三字节模式中,也不会在四字节中;三字节模式也不会在四字节模式中,这样就解决上面所说的搜索匹配难题。你可以先想想看为什么,下面的图以二,三字节为例说明了为什么。
可以看到,由于固定位上的0和1的差别,使得二字节既不会与三字节的前两字节相同,也不会它的后两字节相同。其它几种情况原理也是如此。
利用代理区作区分
让我们再来看另一种变长方案。用所谓代理区来实现。
这里挖出70-89间的码位,形成横竖10*10的编码空间,使得能再扩展100个编码空间。原来2位100个空间损失了20还剩80,再加上因此而增加的100个空间,总共是180个空间。这样一种变长方式也就是UTF-16所采用的,具体的实现我们留待后面再详述。
好了,关于定变长的问题,就讲到这里,下一篇将继续探讨前面提及但还未深入分析的一些问题。
& 著作权归作者所有
人打赏支持
领取时间:
领取条件:参与过开源中国“源创会”的 OSCer 可以领取
码字总数 154046
这篇文章 最后有点地方没看明白不过 这几篇文章写的太牛逼了支持一下
引用来自“smallsun512”的评论您好,请问关于对字符集编码的了解,要参考哪些资料,我是个初学者求指教我也没发现什么专门讲字符集编码的书,去网上搜索一下就能找到很多谈论编码的文章,当然,想比较权威准确的话,比如Unicode方面,我主要是参考Unicode组织的官网;讲到GB2312时,因为它是国家标准,参考的也是国家标准组织发布的标准原文。
您好,请问关于对字符集编码的了解,要参考哪些资料,我是个初学者求指教
引用来自“zhuqm”的评论期待你的下一篇文章 谢谢,看见你好几次留言。其实再看了下这篇,感觉还是有些拖沓,写的时候觉得这个主题有些单薄,所以就往深里扯了,现在感觉步子迈得有点太大,扯远了,主题不够鲜明,纯粹是锻炼了一下画图跟扯谈的能力。
期待你的下一篇文章
评论删除后,数据将无法恢复
注:由于两边同步的麻烦,更多更改及调整可参考我的网站:xiaogd.net 上的字符集编码与乱码系列,已将字符集编码系列与乱码探源系列合并,更新及勘误等不再更新到这边。 在深入研究字符集编码...
注:由于两边同步的麻烦,更多更改及调整可参考我的网站:xiaogd.net 上的字符集编码与乱码系列,已将字符集编码系列与乱码探源系列合并,更新及勘误等不再更新到这边。 前面谈到不少的Unico...
注:由于两边同步的麻烦,更多更改及调整可参考我的网站:xiaogd.net 上的字符集编码与乱码系列,已将字符集编码系列与乱码探源系列合并,更新及勘误等不再更新到这边。 许多时候,字符集与编...
1、定长表与定长表分离 varchar text blob 变长表单独存放 区别: 变长表:包含varchar,text等变长字段的数据表,就是变长表 定长表: 不包含变长类型的字段的表 定长表优点: 变长表由于记录...
小小人故事
优点 UTF-8 兼容 ASCII 能适应许多 C 库中的 0 结尾惯例 没有字节序问题 良好的多语种支持(相对 GBK 等跟语种绑定的编码方式) 以英文和西文符号比较多的场景下(例如 HTML/XML),编码较短...
低至一折起
没有更多内容
加载失败,请刷新页面
引言 好久不见,都还好吗? 大家都知道,我一般都是带来实用的东西,这次也一样,我们来试试项目开发中的日志处理。 理论知识 1、为什么要用日志?你是否因为项目出现问题,查找日志文件定位...
成都_小冯同学
背景 由于前面几篇文章涉及的问题 分布式事务的思考 在我们微服务的场景下 各种本地事务都没有办法控制的很好。 A服务调用B服务 当B服务成功提交之后 A发生了异常 这种情况下要如何处理呢??...
通过pre标签进行格式化展示,使用JSON.stringify()方法转换。 代码如下:
&html& &head& &title&HTML显示json字符串并且进行格式化&/title& &/head& &body& &p id="show_p"&{ "name": "B......
writeademo
php-fpm的pool ovim /usr/local/php/etc/php-fpm.conf //在[global]部分增加include并删除我们之前www池子 o include = etc/php-fpm.d/*.conf o mkdir /usr/local/php/etc/php-fpm.d/ o ......
chencheng-linux
shape变化 (?, 784)(?, 28, 28, 1)(?, 14, 14, 8)(?, 7, 7, 8)(?, 1, 1, 8)(?, 8)(?, 10) 结果对比,基本上还是有点用的 0..8 0.3 0.9234 0....
没有更多内容
加载失败,请刷新页面
文章删除后无法恢复,确定取消删除此文章吗?
亲,自荐的博客将通过私信方式通知管理员,优秀的博客文章审核通过后将在博客推荐列表中显示
确定推荐此文章吗?
确定推荐此博主吗?
聚合全网技术文章,根据你的阅读喜好进行个性推荐
指定官方社区
深圳市奥思网络科技有限公司版权所有}

我要回帖

更多关于 字符字符集 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信