细说字符集编码
前言
在开发过程中,难免会遇到不同字符集之间的转换错误问题,常常表现为系统中返回的中文字符串到了浏览器中会变为乱码,这种问题出现的根源在于系统在解析字节的时候没有使用对应的字符集。那么什么是字符集,现在都有哪些常见的字符集,不同字符集的区别是什么?本篇文章将对这些内容点进行尽可能详细的介绍,希望对各位读者有所帮助。
说在前面
聊字符集之前,不妨先聊一下什么是编码?
所谓编码,其实是用来代表某实体或实体某实行的一种符号,通常由数码、字母组成。通俗一点来说,编码就是某个规范下实体的唯一标识,例如学生号就是在校学生在学校内的唯一标识。
所谓字符集编码,其实就是定义了一套数据映射规则,定义了不同字符对应的唯一标识,仅此而已。只是说当前不同的系统可能使用了不同的字符集编码,那么相同的字符在不同的字符集下的唯一标识可能不同,甚至可能不存在,从而导致数据在交互过程由于编码不一致而产生了错误。
而本文则会对目前常见的字符集进行介绍,让各位读者了解我们都有哪些常见的字符集。
一、ACSII字符集
我们知道,计算机底层存储的数据并不会是直接的中英文字符,而是0和1组成二进制的数字,所以要想让我们的字符可以让电脑识别,我们需要制定一套编码规范,来规定哪些字符对应的二进制值是多少。同时,如果不同的组织都各自有自己的编码规范的话,势必会对后面不同组织间的数据通信造成阻碍。
基于上述原因,美国国家标准学会(American National Standard Institute , ANSI )制定了一套美国信息交换标准代码,也就是大名鼎鼎的ACSII字符集的由来,它最初是美国国家标准,供不同计算机在相互通信时用作共同遵守的西文字符编码标准,后来它被国际标准化组织(International Organization for Standardization, ISO)定为国际标准,称为ISO 646标准。适用于所有拉丁文字字母 。
从上图中我们可以看到,ACSII表主要为英文字母(大小写)、数字、标点符号、特殊字符制定了二进制编码(更确切的说,是给这些字符定义了一个十进制的码点,再映射到二进制的数值上),例如字母a
对应二进制为01100001
(对应十进制就是97),字母A
对应的二进制为01000001
(对应十进制就是65)。至此,我们就可以利用这个编码规范来完成相关字符在计算机的存储和通信操作了。
一点小细节
(一)ACSII编码下的字节大小
细心的读者可能会发现ACSII编码一共就只定义了128个字符的编码,也就是说最多也只要7个比特位就可以表示所有字符了,但是实际上计算机存储的最小单位是字节,所以实际上在保存数据的时候,这些字符换算成二进制数字后都会在首位补上一个0
来凑成一个字节,且ACSII编码下的所有字符,都是只占一个字节单位的大小。
(二)ACSII字符集的局限性
需要注意的是,由于ACSII字符集是美国组织所提出的,所以当时只考虑了英文和特殊符号的编码,并没有考虑中文以及其他国家的编码格式。也就是说,当你的文本都是由英文字符组成的时候,那么ACSII字符集确实能够满足使用要求,但是如果说其他国家想要存储自己国家的字符的话,ACSII字符集就无法满足需求了。
PS:ACSII字符最高位置0的情况下,最多表示128个字符,英文字符完全可以被编码,如果是其他语言,字符数量多于127个如何表示呢?一些国家对ASCII码做了扩展,让最高位也参与编码,这样ASCII码能表示的字符数量从128个上升到256个,这种编码ASCII也被成为扩展ASCII编码。
二、ISO8859-1字符集
我们在上一章中提到了,ACSII字符集最高位并没有真正使用到,且随着计算机的发展,人们逐渐发现,ASCII字符集里的128个字符已经不能再满足他们的需求了。人们就在想一个字节能够表示的数字(或者说字符编号更准确)有256个,而ASCII字符只用到了0x00~0x7F,也就是占用了前128个,后面128个数字不用白不用,因此很多人打起了后面这128个数字的主意。可是问题在于,很多人同时有这样的想法,但是大家对于0x80-0xFF这后面的128个数字分别对应什么样的字符,却有各自的想法。因此出现了很多不同的字符集,比如:ISO8859-1,ISO8859-3等。。。这些字符集的前128个字符都与ASCII中相同,也就是兼容ASCII字符集,但后128位却各不相同。ISO8859-1就是属于西欧语系中的一个字符集,比如支持表达阿尔巴尼亚语、巴斯克语、布列塔尼语、加泰罗尼亚语、丹麦语、等,目前使用的最普遍。ISO8859-1有个熟悉的别名:Latin1,简单理解的话,ISO8859-1就是第一章所说的拓展ACSII编码,因为ISO8859-1是tomcat的默认编码格式,java开发者接触相对较多,所以这里就单独拿出来讲一下。
三、 GB2312字符集
GB2312是中国国家标准简体中文字符集,也是中国最早推出的字符集。由于中文常用的字符多,而原有的ACSII字符集只有1个字节的长度,无法满足编码需要。所以GB2312使用两个字节来表示汉字,并规定:一个小于127(指一个字节表示的十进制数值)的字节的意义与原来相同,但两个大于127的字节连在一起时,就表示一个汉字。前面的一个字节(称之为高字节)使用0xA10xF7这个区间,后面一个字节(低字节)使用0xA10xFE这个区间,这样我们就可以组合出大约7000多个简体汉字了。
在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,同时GB2312字符集在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。这种汉字方案叫做 "GB2312"。GB2312 是对 ASCII 的中文扩展。兼容ASCII。GB2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。
PS:GBK编码同时也兼容了ACSII编码,也就是说对于ACSII原有的字符,它们还是会按照1个字节来进行存储。
四、GBK编码
虽然GB2312基本满足了我国人在计算机上对汉字的需求。但是中国的汉字太多了,对于人名、古汉语等方面出现的罕用字,GB2312不能处理。不得不继续把GB2312没有用到的码位找出来用上(GB2312编码中,高字节的编码范围是0xA10xF7,也就是161247,而不是128~255,这就造成了浪费)。后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始。这个编码方案被称为 “GBK” 标准,GBK包括了GB2312的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。
所以GBK是GB2312的扩展,(“K”就是“扩”字拼音的首字母),因此完全兼容GB2312。
一点小细节
上文我们提到,GBK字符集使用2个字节来表示一个汉字,1个字节来表示一个字母(也就是标准ACSII里面的字符还是用的1个字节),那么当文本中既有中文,又有英文的时候,系统是怎么知道每次是要读一个字节还是两个字节呢?
GBK中规定,汉字的第一个字节首位必须是1。比如我a你
,对应的编码就会是下面这种格式:
1xxxxxxx xxxxxxxx 0xxxxxxx 1xxxxxxx xxxxxxxx
当程序在解码的时候,读到的字节首位是1时,就会判断这个字符是中文,按2个字节来截断读取,当读到字节的首位是0时,就会判断这个字符是标准的ACSII字符,按1个字节来截断读取。
说白了,GBK字符集其实也是一套字符和二进制字节的映射表,只是GBK字符集使用2个字节来作为一个单元存储数据,使其可以支持数量上万的字符编码。
五、Unicode字符集
随着计算机发展到世界各地,不同国家和组织由于各自的字符并不能被ACSII字符集所涵盖,但又需要自己的字符可以让计算机识别,所以出现了很多既兼容ASCII但又互相不兼容的各种编码方案。这样一来同一个二进制编码就有可能被解释成不同的字符,导致不同的字符集在交换数据时带来极大的不便。
此时,Unicode字符集就应运而生。Unicode的名字起的很形象:该字符集是为了给全世界所有字符一个唯一的编码,“唯一”对应的英文为Unique,而编码的英文为code,两者结合一下就产生了Unicode。
需要注意的是,和原先其他字符集不同,Unicode本身并不直接提供某个字符的字符编码,而是将字符集和字符编码方案分离开,也就是说,虽然每个字符在Unicode字符集中都能找到唯一确定的编号(字符码,又称Unicode码,每个Unicode码占2个字节),但是决定最终字节流的却是具体的字符编码。例如同样是对Unicode字符“A”进行编码,UTF-8字符编码得到的字节流是0x41,而UTF-16(大端模式)得到的是0x00 0x41。(UTF-8和UTF-16是unicode字符集下的不同字符编码
)
Unicode
编码在javaweb开发中的国际化配置文件也得以体现,javaweb允许我们配置不同的国际化文件来方便程序跟进不同地区的请求返回对应国家语言的相应内容。所以类似GBK的编码肯定是无法满足国际化的需要。而unicode字符集就能够很好的满足我们的需要,通过写入唯一的unicode码,再让具体的编码方案去生成对应的字节码,只要双方都使用unicode字符集下的同一个编码方案,交互就可以顺利进行。
在笔者看来,unicode编码伟大的地方在于让全球使用同一种字符集进行数据交流成为可能,另外就是将字符集和字符编码进行拆分,大大提高了编码的灵活性
六、UTF-8字符集
UTF-8是Unicode字符集中所采用的具体的字符编码,它完美实现了对ASCII码的向后兼容,以保证Unicode可以被大众接受。在UTF-8中,0-127号的字符用1个字节来表示,使用和ASCII相同的编码。这意味着1980年代写的文档用UTF-8打开一点问题都没有。只有128号及以上的字符才用2个,3个或者4个字节来表示。因此,UTF-8被称作可变长度编码。下面讲解一下UTF-8的编码方式。
如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用1个字节的空间。0之后的所有部分(7个bit)代表在Unicode中的字符编号。这部分其实就是兼容ACSII字符集编码。
如果一个字节以110开头,那么代表当前字符为双字节字符,占用2个字节的空间。110之后的所有部分(5个bit)加上后一个字节的除10外的部分(6个bit)代表在Unicode中的字符编号。且第二个字节以10开头,所以如果一个国家或地区所使用的字符只要11位之内(也就是小于2048个字符)就能完全涵盖的话,那么基本上两个字节就可以满足需要了。
如果一个字节以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间。1110之后的所有部分(4个bit)加上后两个字节的除10外的部分(12个bit)代表在Unicode中的字符编号。且第二、第三个字节以10开头。由于我们国家所使用的汉字较多,在UTF-8的字符编码下,2个字节无法满足需要,所以中文字符在UTF-8编码中是占了3个字符
(所以从这点来看,UTF8编码的中文的存储空间是GBK编码的1.5倍)如果一个字节以11110开头,那么代表当前字符为四字节字符,占用4个字节的空间。11110之后的所有部分(3个bit)加上后三个字节的除10外的部分(18个bit)代表在Unicode中的字符编号。且第二、第三、第四个字节以10开头
总结
本篇文章对当前国内常用的字符集进行了介绍,介绍的顺序也基本是按照这些字符集出现的时间来的,字符集编码从ACSII
起步,出于满足不同国家的需要而延伸出了不同的编码集,像我们国家的话就是出现了GB2312
和后面字符内容更丰富的GBK
,为了统一不同国家间的交互需要,又有了Unicode
字符集来进行统一字符集,本质上来说不同的字符集就是不同发展阶段的产物。文章的内容相对比较枯燥,但是如果整篇文章看下来的话,相信可以让读者对字符集的发展历史以及其实现细节上有一个比较好的认识。
同时本篇文章也参考了网络上的一些优秀的文章,在此也十分感谢之前其他作者的分享~
参考文章:
彻底解决乱码问题(三):详细分析常用字符集(ASCII,ISO8859-1,GB2312,GBK,Unicode)和字符编码(UTF-8,UTF-16)