ASCII、GBK、Unicode、UTF-8、ISO-8859-1等常见字符编码介绍
通过计算机编码的诞生和发展历程,理解为毛有这么多种字符编码,以及他们之间的区别。本文主要参考网页编码就是那点事,并进行适当修改。
盘古开天之ASCII编码出现
最早的时候,是在学习C语言的时候接触的ASCII编码(American Standard Code for Information Interchange,美国信息交换标准代码),知道是一种使用二进制(一个字节表示一个字符,8bit)来表示计算机的一些控制字符、通信字符、大小写英文字符、阿拉伯数字以及一些标点运算等符号。由于是美国人制定的规则,当时只考虑了英语体系用到的一些符号(自私自利的美帝国主义,手动滑稽)。这种方案在当时计算机刚出现不久还能凑合使用,并且当时的存储成本也比较高,使用一个字节来存储也节省很多存储资源,同时也提高了传输效率。由于计算机技术发展迅猛,很快欧洲、中国、日本也开始开展自己的计算机研究。这时大家发现WTF,计算机不认识自己的母语呀,这怎么能行,计算机也文化歧视?各个国家开始搞事情了。
春秋战国之各家编码
首先发难的是欧美拉丁系国家,他们发现ASCII编码只用了从0000 0000(二进制)到0111 1111(二进制)128种符号,浪费啊浪费。他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称"扩展字符集",也就是Latin-1字符集,即ISO-8859-1。
这下欧美发达国家是够用了,但是老美没想到第三世界的国家也需要使用计算机吧。勤劳智慧的中国人民说,“这都不是事儿”。我们发扬"拿来主义"精神,取其精华,去其糟粕。把ASCII编码的127位及之前的字符保留着,127位之后的那些乱七八糟的字符通通干掉。并且规定:一个汉字使用两个字节表示。,一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE。一共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。两个字节长的就是"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
这种字符方案叫做GB 2312字符集,已经将常见的使用频率较高的一些汉字收录进去,基本能够满足绝大多数中国人民使用啦。
But,要知道中国文化博大精深,还有很多生僻字没有办法使用,万一刚好是某个领导的名字呢(手动尴尬)。于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准。GBK兼容了GB 2312的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。
但还要团结少数民族呀,于是又提出了GB 18030,除保留全部GBK编码汉字,在第二字节把能使用范围再度进行扩展,增加了大约一百个汉字及四位元组编码空间,将GBK作为子集全部保留。
由于这些汉字编码标准都是采用两个字节表示一个汉字,统称为"DBCS"(Double Byte Charecter Set 双字节字符集)。因此开发人员在作中文处理的时候,经常要念一句咒语:"一个汉字算两个英文字符!一个汉字算两个英文字符……" 只有经过咒语加持的代码才能正常运行。
可是中国大陆用的是简体字呀,虽然GBK已支持繁体字,当时台湾、香港、澳门等繁体中文通行区的人民特立独行,自己搞了一套Big5编码。还有其他国家的也各自为政,韩文编码EUC_KR字符集,日文编码Shift_JIS字符集,俄文编码KOI8-R字符集等等。书不同文,这简直是阻碍人类文明在计算机世界的发展啊。
书同文之Unicode救世
为了结束这种乱象,这时ISO(国际标准化组织)站出来了,他们绝对为世界650种语言进行统一编码。经过研究,终于整了一个Unicode字符集出来。由于当时计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ascii里的那些“半角”字符,Unicode 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于"半角"英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。 而且原来的很多程序在计算字符串长度也不准确了,一个中文和一个英文都是一个字符!此外,Unicode它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储,以及如何进行传输。由于这些局限,导致Unicode只能是一种理论上的标准。
众望所归之UTF-8出世
随着互联网的兴起,面向传输的众多 UTF(Unicode Transformation Format)标准出现了,UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。画重点,这里的关系是,UTF-8 是 Unicode 的实现方式之一。UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8编码规则:
-
对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
-
对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
例如"汉"字的Unicode编码是6C49。6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 1100 0100 1001,再依序替代模板中x,得到:1110-0110 10-110001 10-001001,即E6B189,这就是"汉"字的UTF-8编码。
常见问题
Big endian与 Litter endian
使用UCS-2存储Unicode编码(也就是一个字符使用两个字节存储),由于计算机架构的不同,比如,"汉"字,Unicode编码16进制,使用Big endian(大头方式)表示为6C49,使用Litter endian(小头方式)表示为496C。那么在网络传输及本地文件存储时,计算机怎么知道使用何种方式呢?Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(zero width no-break space),如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。
有了UTF-8为何还要使用GBK编码
既然现在已经有了一个统一的UTF-8编码来表示所有语言来,为什么现在很多程序以及系统仍旧还在使用GBK,Big5,ISO-8859-1等这些编码呢?我觉得主要有两点因素:
- 兼容问题。由于之前的历史原因,导致很多系统及程序应用是使用其他编码来开发的,而UTF-8又不能兼容之前的编码,如果要继续使用也只能用对应的编码方式。
- 资源问题。由于UTF-8编码是变长的,需要使用1~4个字节来表示一个字符,如果是汉字的话通常需要2~4个字节来表示。而使用GBK编码只需要2个字节。当你的文本内容及使用的对象主要是中文环境时,使用GBK编码将大大减少你的存储空间、传输速度及网络流量。
Java字符串编码问题
对于Java以及tomcat遇到的一些编码问题,可参考深入分析Java中的中文编码问题。该文章已经描述得十分透彻。在这里就简单说一下在Java编码中遇到中午乱码的几个处理方式:
-
对请求参数进行编码转换
String userName=request.getParamter("userName");
userName=new String(userName.getByte("iso-8859-1"),"utf-8"); -
对请求头进行编码转换
request.setCharacterEncoding("UTF-8");//该方法只对POST方式提交的数据有效,对GET方式提交的数据无效!
-
tomcat默认ISO-8859-1编码,对tomcat服务器server.xml文件设置编码,即可让tomcat以UTF-8的编码处理get请求。
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>