20190628 ASCII、ISO-8859-1、Unicode、UTF-8、GB2312之我的理解

写作初衷

工作中,时不时会遇到乱码问题。总是匆匆看看几篇文章然后自己再捣鼓会,也能把问题给解决掉,但其实没有真正搞懂这一块的知识。

也很想要把这块知识给搞懂。于是乎一次又一次的看了网上的博客文章,自己也思考了一番,于是尝试试着做些总结归纳来输出一下,给大家参考参考。

我写这篇文章,不想上来就把UTF-8,GBK,ISO-8859-1、Unicode、ASCII的概念、作用、区别扔给你看。因为我觉得这样并不能真正的理解,或者说需要你本身已经理解了许多知识,这些知识有如什么是编码,什么是字符集,为什么会产生ASCII编码,为什么ASCII编码使用一个字节就够了,为什么一个字节是八位,为什么要对ANSI ASCII进行扩展,为什么会产生Unicode码,以及Unicode、gb2312、ASCII、UTF-8的关系。

于是我尝试用我的思路来给大家介绍我对这块知识的理解,既然是我的理解,自然会有疏漏之处,还可能有错误的地方,如有问题,还请指出。

那就开始啦。

工作中遇到乱码

在工作中,在涉及网络传输数据的时候,以及在读写文本文件的时候,都有可能出现乱码问题。

  • 如对接第三方系统时,通过接口进行数据传输,如果双方编码不一致,就会出现数据乱码。
  • 又如我在接收别人项目的时候,当我使用开发工具打开这个项目代码的时候,会发现,中文注释以及中文代码都变成了乱码。
  • 又如早期在学java web的时候,用servlet写接口,用jsp做前端数据展示。刚开始接触的时候,会出现页面乱码,后台接收数据乱码的问题。

我们常说这是因为编解码不一致导致的。这里说的编解码的编码,跟接下来要讲的编码不是一个意思。

总之,我们在工作中,难免会遇到乱码问题。为了搞懂它,我们就需要了解隐藏在问题背后的知识。

编码

首先来介绍一个词——编码。它既可以作为动词,又可以作为名词。因此它在不同语境下有不同的意思,而作为动词,又有两种不同的意思。

在计算机中,数据的存储和运算都要使用二进制表示(计算机用高电平和低电平表示1和0)。而具体用哪些二进制数表示哪个字符,每个人都可以自己约定一套规则。而如果想要在相互通信时不造成混乱,那么最好都使用同一套标准。

用指定的二进制数表示指定的字符这个操作,就是编码(这里的编码是动词)。因此,所谓编码,就是将每个字符映射为对应的二进制数,这样就能就计算机中用这些二进制数来表示字符了。映射函数不同,也就有了不同的字符集编码了。于是就有了ASCII编码、GB2312编码、Unicode编码(这里的编码是名词),这里的名词编码,也就是字符集的意思。那什么是字符集。

前面介绍了编码作为名词的意思,以及作为动词的其中一个意思,编码作为动词还有另一个意思,就是我们前面提到的 乱码问题通常是由于编解码不一致导致的 中的编码,这个在介绍Unicode的时候会再提到。

这一段,想表达的是我个人对编码的理解,以及强调一个信息:在计算机中,数据的存储和运算都要使用二进制表示。正因数据在计算中要用二进制数表示,才有了ASCII编码、GB2312编码、Unicode编码、UTF8等其他东西。

字符集

然后,再来介绍下字符。

字符(Character)是各种文字和符号的总称,包括个各国家文字、标点符号、图形符号、数字等。字符集(Character Set)是多个字符的集合,字符集合种类较多,每个字符集包含的字符个数不同,常见字符集名称:ASCII字符集、GB2312字符集、GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,就需要进行字符编码,以便计算机能够识别存储各种文字。不同的字符集,其实也就是编码方式不一样。

我们说对字符进行编码,也就是为用一串二进制数来表示一个指定的字符。这样理解的话,当我们看到ASCII编码、Unicode编码的时候,也就不至于造成混乱了:为什么一下子是ASCII编码、一下子又是ASCII字符集。其实它们表示的是一个意思。后续提到的ASCII码、ASCII编码、ASCII字符集都是同一个意思。都是表示字符集映射为二进制数的这张字典表。

为什么上面没有提到我们经常接触到的UTF-8呢?因为UTF-8并不是一种字符集,这个疑问我们留到后面再说。

ASCII码介绍

前面说了,在计算机中,数据的存储和运算都要用二进制表示。每个人都可以自己订一套协议标准,但是这样不利于不同计算机之间的通信交流。因此,为了让数据在不同计算机中能正常的传输,美国国家标准学会(American National Standard Institute ,ANSI)制定了一个标准的单字节字符编码——ASCII编码。这个标准ASCII,又成为ANSI ASCII。

这个ANSI ASCII,制定出来的初衷,是供不同计算机在相互通信时用作共同遵守的西文字符编码标准。

注意,是供不同计算机在相互通信时用作共同遵守的西文字符编码标准。注意注意,是西文西文西文。也就是说跟我们中文无关,以及希腊文、俄文、法文等等是无关的。正因如此,各个国家对ASCII码进行了扩展,形成了不同的扩展ASCII码。而我们中国的汉字多达几万,ASCII是单字节字符编码,最多只能表示256个字符,无法满足我们。因此我们使用的是多字节字符编码,有如GB2312、GBK、GB18030。至于他们之间的区别,只是涵盖的汉字范围不同,这里不做介绍。

对了,为什么ANSI会把ASCII指定为单字节字符编码呢?为什么是一个字节,或者说为什么是8bit呢?因为西文中的字符数在127个以内(大写小写字母,数字0~9,以及在美式英语中使用的特殊控制字符加起来,最多不超过127个字符)。

字节原义就是用来表示一个字符。

ANSI ASCII只用了7位(7位已经满足编码要求,7位足以涵盖大写小写字母,数字0~9,以及在美式英语中使用的特殊控制字符),可是为什么一个字节不是7位而是8位呢?这是因为计算机采用的是二进制,因此计算机中一个信息量单位都是2的n次方,因此一个字节是8位,当然16位也可以,但是还得考虑当时的技术、硬件。8位是是最好的选择,16位则造成浪费。因此一个字节占8位。

因此,当欧美国家对ANSI ASCII编码进行扩展的时候,利用字节中闲置的最高位编入新的符号。比如法语中的é的编码为130(10000010)。这样,一个字节最多就可以表示256个字符了。但是不管怎样,所有这些编码方式中,0~127表示的符号是一样的,不一样的只是128~255的这一段,为的是与ANSI ASCII保持兼容。

ANSI ASCII的推出,是为了让不同计算机在相互通信时用作共同遵守的西文字符编码标准。可是设计者没有考虑到其他国家的文字,导致了出现了各个扩展版本的ASCII编码以及其他的亚洲文字编码。这就导致数据在不同国家计算机中传输时会出现数据无法被正确解析。比如,130在法语编码中代表é,在希伯语编码中却代表字母Gimel (ג),在俄语编码中又会代表另一个符号。至于中文汉字就更加没法在单字节字符编码ASCII码中正确表示了。

乱码以及数据解析不正确由此而生。

ISO-8859-1

终于介绍ISO-8859-1了,ISO-8859-1就是扩充版的ASCII编码,又称为Latin-1。该编码是单字节字符编码,因此它并不支持中文字符。尽管如此,在许多协议上,依然默认使用这个编码。因此在工作中我们经常接触到它。

Unicode

前面说了,不同国家对ANSI ASCII进行了扩展,甚至创建了其他的编码,导致编码不统一的问题。另一个问题是,很多传统的编码方式都有一个共同的问题,即容许电脑处理双语环境(通常使用拉丁字母以及其本地语言),但却无法同时支持多语言环境(指可同时处理多种语言混合的情况)。

为了统一所有文字的编码,Unicode字符集应运而生,Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案Unicode字符集就像一本超级大字典,涵盖了所有字符,为每一个字符映射唯一的二进制数。

既然Unicode字符集可以涵盖世界上所有的字符,那么Unicode就肯定不是两字节字符编码,因为两个字节最多只能表示65536个字符。

Unicode最初设计是作为固定宽度的16位字节编码。但显然不够用,那些超出原来的16位限制的字符被称作增补字符,而。目前的Unicode字符分为17组编排,0x0000 至 0x10FFFF,每组称为平面(Plane),而每平面拥有65536个码位,共1114112个。用两个字节足以表示的字符属于0号平面,中文名称是基本多文种平面,英文名称是BMP,即basic multingual Plane。绝大多数常见的的汉字都在BMP中。

另外,由于Unicode不是单字节编码字符,因此Unicode字符集是不兼容ISO-8859-1字符集的,但是它也只是在ISO-8859-1前面补0,加多一个字节而已。

UTF-8

既然Unicode字符集已经足以涵盖所有的字符,这样不就统一编码了吗,不就没问题了吗?考虑到Unicode不兼容ISO-8859-1,并且Unicode占用更多的空间(对于英文字母以及数字等字符,Unicode也需要用两个字节来表示),所以不便于数据的传输和存储,因此催生出了一个既能兼容ISO-8859-1编码,又能节省空间的编码格式——UTF-8。

UTF-8,即8-bits Unicode Transformation Format。它是一种针对Unicode的可变长度字节编码。用1~6个字节来编码Unicode码字符。它是由于Unicode字符集,对于可以用ASCII表示的字符使用Unicode并不高效,因为Unicode比ASCII占用大一倍的空间,为了解决这个问题,就出现了中间格式的字符集,他们被称为通用转换格式,即UTF。

这里要搞清楚UTF-8和Unicode的关系。Unicode是字符集,数据的存储当然可以直接用Unicode来存储,就如ASCII字符集一样。但是前面说的对于ASCII能表示的字符用Unicode表示会更占资源,因此在传输和存储中,一般不会直接存储Unicode,而是采用一种中间格式的字符集,即UTF,也就是说UTF-8字符集是基于Unicode字符集存在的。我们一般不把UTF-8称为字符集,而是把它称为编码格式。但其实理解了这些知识,其实叫什么都一样。我们只要知道UTF8是基于Unicode之上的一种中间格式字符集就够了。

前面说,UTF-8用1~6个字节来编码Unicode字符。Unicode到UTF8的转换方式就参考其他文章。

UTF-8、ISO-8859-1、GB2312、Unicode的关系

现在,大多数软件中的内码,采用的都是Unicode字符集,比如Java。

文章开头曾提到过乱码问题通常是因为编解码不一致导致的。现在再来思考,就很好理解了。

或许出于传输效率的考虑吧,我们在传输的时候不直接传Unicode码,我们通常都是转为UTF8编码,或者GB2312编码,如果数据中不含中文,我们也可以将数据转为ISO8859-1编码。总之,我们在传输数据的时候,都会对Unicode码做编码处理。

那么我们在接收数据的时候,自然也要指定相应的解码方式。也就是说这时乱码的产生是因为我们用错解码方式。

可以这么理解,我们在传输数据的时候,使用UTF8编码处理,相当于用UTF8这本字典来翻译原本的Unicode数据。因此当我们在解析的时候,我们必然也要使用UTF8这本字典来解读,如果此时我们使用GB2312这本字典来解读,或者ISO-8859-1这边字典来解读,就会得出错误的结果,这就导致乱码的出现。

也许你会说我在解析的时候明明没有指明编码啊,其实如果没有指明编码的话,默认会使用系统编码。因此我们写代码的时候,尽可能的避免不指明编码,否则很可能在测试环境是正常的,一旦切换到正式环境就出现乱码。这可能是由于测试环境服务器和正式环境服务器设置的默认编码不一样。

UCS

另外,有时我们会看到UCS-2,UCS-4,那这是什么,跟Unicode有什么关系?

UCS,即Universal Character Set,是由ISO制定的标准字符集。USC-2使用2个字节编码,UCS-4使用4个字节编码。但其实它和Unicode是类似的。UCS和Unicode是由不同组织先后推出的为了统一编码而创建的字符集。

Unicode的两字节形式就类似于UCS-2,Unicode的四字节形式就类似于UCS-4。

总结

嗯,差不多就是这样啦。其实还有些知识还没介绍,像UTF-16、UTF-32。

写这篇文章,参考了很多的文章,自己也是通过不断问自己为什么为什么为什么,从而查阅了更多的文章。才得以领悟这么多知识。也是在这个过程中,感慨大学的一些课程如计算机网络、计算机组成原理、操作系统,其实挺重要的,这些都是基础知识。有了这些基础知识,理解其他东西会更容易。

参考链接

为什么电脑数据一个字节是8位?
ASCII码介绍
字符集介绍
Unicode、UTF-8 和 ISO8859-1到底有什么区别
UTF-8
unicode编码是占用几个字节?
基础数据类型之Unicode编码简介
你真的懂 Unicode 和 UTF-8 是什么关系吗?来看看这个就彻底懂了
不用再为神奇的ASCII,Unicode字符编码头痛了!一文让你弄懂编码

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值