编码的发展/Unicode和ASCII等其他常见编码格式

1.什么是编码?

编码是信息从一种形式或格式转换为另一种形式的过程;解码则是编码的逆过程。

比如说,在电报时代,我们用摩尔斯代码 ·— 来表示字母A就是一种编码过程,而在电报发送者的另一端,接收电报的人将 ·— 翻译为字母A就是解码的过程。

很明显,根据编码方式的不同,一个信息可以被编码为多种表示形式。比如说在人类形成了数的概念之后,有一些人将数字1这个概念编码成了“一”,而又有另外一些人将它编码为“one”。

此外,编码的差异不仅体现在编码方式上,也体现在编码的内容上,不同的内容往往有较大差异的编码方式,而(就像上面所说的)相同的内容也可以由不同的编码方式转换成不同的信息形式。比如说因为编码内容的不同,虽然将语音存储在计算机中也需要将其转换为数字的形式,但这需要运用一种别于将文本编码为数字的编码方法。

根据被编码内容以及编码方式的不同,编码可分为字符编码、文字编码、语义编码、电子编码等等。我们通常所讨论的是字符编码。

前面虽然将编码是一种信息转换的过程,但就像其他很多词语一样,编码既可以作为动词用来表示改变信息形式的过程,也可以作为名词用来表示一种转换信息形式的方法。比如,我们既可以说将英文字母A表示为 ·— 是在进行编码,又可以说摩尔斯电码是一种编码。总之,编码以及下面将要提到的字符编码往往有着动词和名词两重意思。

字符编码:

广义的字符编码是指一套编码法则(或者称方法、方式等),使用该法则能够对自然语言的字符的一个集合(如字母表、音节表、汉字),与其他东西的一个集合(如号码或电脉冲)进行配对(也就是形成映射关系)。

比如,小明与他的朋友约好在今晚双方将通过QQ发送的字符“¥”表示为“有钱去吃夜宵”,而字符“!”表示“爸妈没给零钱”,这也算是一种字符编码,它将字符集合(¥,!)对应于另一个集合(有钱去吃夜宵,爸妈没给零钱)。

狭义的字符编码就是指那种能够将字符集合(字符的集合,又称字符集)表示成易于计算机处理文本的形式的编码法则。也就是说,产生狭义的字符编码是为了使得计算机能够比较好地处理文本形式的信息。

就像前面所说的,字符编码有两重意思,也可以作为名词用来表示被编码后的字符,在这种时候,它有时也被称为字集码,比如说,我们可以说摩尔斯电码 ·— 是字母A的字符编码或字集码。

下文所要讨论的就是狭义的字符编码,为了方便,下文直接用编码表示。

2.为什么要有编码?

首先大家需要明确的是在计算机里所有的数据都是以二进制数字的形式存储和处理的。我们想要让计算机帮我们处理文字信息,首先要建立起文本信息与二进制数字之间的编码关系,也就是将字符对应于计算机中的二进制数字方便计算机“模拟”处理文字信息。

二进制数字本身是不能表示文本的,如果不进行约定(也就是编码),这些二进制数除了能够表示数字本身没有任何意义。这就像是如果我们不约定"1"可以用来表示“一棵树”、“一个苹果”、“一台计算机”这个数量,符号"1"本来不带任何意义。

所以编码就是为了给这些字节赋予实际的意义,制定各种编码标准,使得计算机不仅能够帮我们进行如31321+131这样的数学计算,也能够帮我们处理文本信息并在需要的时候将它们正确地显示出来。

好了,既然是为了让计算机帮我们处理文本信息,那究竟该如何编码呢?像其他事物一样,编码也不可能一下子被完善,它有着自己的历史演变。

3.编码的发展史

原始时代

之前说过,编码是为了方便表示文本形式的信息。在计算机还不够流行的时代,人们自然顾不得全世界计算机之间的沟通交流,只要自己的计算机能够正常运行就很好了。于是在20世纪的40年代~50年代,人们设计了许多的代码用以表示文本,并结合不同的设备使用,但随着计算机的发展,人们发现在某一种编码方式下能够正确显示的文本放在另一种编码方式下的计算机中,往往就不能够正确显示而出现乱码,这无疑是不便于信息交流与传递的。

ASCII时代

为了缓解编码方式的不同带来的交流问题,ANSI(American National Standards Institute,美国国家标准化学会)制定了ASCII(American Standard Code for Information Interchange,美国信息交换标准码)。

ASCII 出现在上个世纪 60 年代的美国,一共定义了 128 个字符,因为那时候的ASCII只使用了一个字节的 7 位,字节的左边第一位置0不使用,所以有时也被叫做标准ASCII码基础ASCII码。其定义的字符包括英文字母 A-Z,a-z,数字 0-9,一些标点符号和控制符号。在 Shell 里输入man ASCII,可以看到完整的 ASCII 字符集。ASCII 里直接定义了字符与二进制代码之间的对应关系,比如说字符A 对应于二进制数0100 0001

在英语系国家里 ASCII 标准很完美。但是不要忘了世界上可有好几千种语言,这些语言里不仅只有这些符号啊。如果使用这些语言的人也想使用计算机,ASCII 就远远不够了。所以到这里编码进入了混乱的时代。

混乱时代

人们知道计算机的一个字节是 8 位,可以表示 256 个字符。ASCII 却只使用了 7 位,所以人们决定把剩余的一位也利用起来,这些被扩展的ASCII码自然也就被称为扩展ASCII码(EASCII)了。扩展是挺好,可随之而来的信息交流问题也慢慢显现。

人们对于已经规定好的 128 个字符是没有异议的,但是不同语系的人对于其他字符的需求是不一样的,所以对于剩下的 128 个字符的扩展会千奇百怪,其中就包括IBM PC和MS-DOS使用的代码页437(Code page 437)和国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的ISO/IEC 8859。而且更加混乱的是,在亚洲的语言系统中有更多的字符,一个字节无论如何也满足不了需求了。例如仅汉字就有 10 万多个,一个字节的 256 表示方式怎么能够满足呢。于是就又产生了各种多字节的表示一个字符方法(GBK 就是其中一种),这就使整个局面更加的混乱不堪。

有人可能会想,经过拓展的ASCII虽然乱了一点,但只要可以满足计算机操作的需求不就很好了吗,的确,如果国与国之间的计算机联系不强,人们只想要在家里用计算机大大小游戏写写日记的话,我们其实没有多大理由再进一步发展编码方式,可人们终究是需要相互联系的,即使是再好玩的游戏也需要有不同的玩家加入才足够有乐趣,即使是写得再好的文字没有人看几眼也多少少了点光彩,于是乎,人们一方面进一步使得计算机变得不再是一种奢侈品,另一方面又发展它的性能,并将它与外界的联系扩展到全世界——互联网时代悄然到来。就像当初计算机的增多使得ASCII出现而终结了原始时代一样,互联网的发展又同样将使得另一种编码方式诞生而终结混乱时代。

虽然当时比较流行的ISO开发的一系列ASCII拓展在支持全世界多语通信方面取得了巨大进展,但是仍有两个主要障碍。首先,扩展的ASCII中额外可用的数字不足以容纳许多亚洲语言和一些东欧语言的字母表。其次,因为一个特定文档只能在一个选定的标准中使用符号,所以无法支持包含不同语种的语言文本的文档。实践证明,这两者都会严重妨碍其国际化使用。为弥补这一不足,Unicode在一些主要软硬件厂商的合作下诞生了。

Unicode时代

要想解决混乱时代的交流问题,无疑需要制定一种适合全世界语言的编码方式。到了字符编码问题严重限制了计算机行业发展的时候,逐渐产生了两个独立的尝试创立单一字符集的组织,即

  • 国际标准化组织(ISO)于 1984 年创建的 ISO/IEC JTC1/SC2/WG2。

  • 由Xerox、Apple 等软件制造商于 1988 年组成的统一码联盟

我们之前提到ISO扩展过ASCII编码,其实,ISO不仅在前期扩展了ASCII编码,还在后期组织了对统一字符集的编码。ISO开发了ISO 10646(或称 ISO/IEC 10646 )项目,而统一码联盟开发了统一码(Unicode)项目。并且两者最初制定了不同的标准。

1991 年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。1991 年,不包含 CJK 统一汉字集的 Unicode 1.0 发布。随后,CJK 统一汉字集的制定于 1993 年完成,发布了 ISO 10646-1:1993,即 Unicode 1.1。

Unicode在这两个组织协同下发展,虽然在现在计算机中一般都只出现Unicode的身影,但统一码联盟和ISO都为Unicode做了贡献。但不管Unicode的发展经历了哪些细节,一直以来编码的目的都始终不变,Unicode也不例外。

Unicode给计算机中所有的字符各自分配一个代号(通常用十六进制表示),它建立了大量字符与代号之间的对应关系,并在后来发展的过程中不断扩充字符集的内容,当然了,其内容不只是建立了字符与代号之间的对应关系那么简单,还包含了更全面更深层的各种细节。

在有些时候,Unicode作为一个名词只是代表着字符与代号之间的对应关系,比如我们有时说某一个符号的Unicode是多少,就是指这个符号的Unicode代号。而更多的时候,我们将它视作一种编码方式、一个编码系统。这个系统不仅规定了字符与数字之间的对应关系,还具体到将字符对应于数字在计算机中该如何实现,总之,计算机编码方式发展到这里,已经比ASCII时代要完善得多,复杂度也不是一个级别了,也正因为如此,Unicode才会发展出不同的实现方式——这一点与ASCII有很大不同,下文将会提到。

但抛去那些细节,Unicode和ASCII以及计算机里常见的其他编码方式一样,都仅仅是一种方便计算机处理文本的工具而已,所以当我们在计算机里看见各种各样的编码方式时,应该明白,尽管它们有各自的适用范围和优势,但就功能而言,它们都是为了同一个目的,那就是以不同的实现方式方便计算机处理文本信息。

5.Unicode

Unicode之所以不同,不仅仅是将全世界的几乎所有字符编码为一个数字那么简单,仅仅这样如果能够解决根本问题,ASCCI之后就不会出现各种各样的拓展而迟迟不统一。事实上,编码不统一一方面是因为互联网还未很好地发展起来,计算机使用者交流的意愿还不够强,另一方面多多少少也是因为人们还没能找到编码统一的解决方案。

Unicode 首先将我们需要表示的字符表中的每个字符映射成一个数字(通常用十六进制来表示),这个数字被称为相应字符的码点(code point)。例如“严”字在 Unicode 中对应的码点是 U+0x4E25。

到目前为止,还只是建立起字符与数字之间的对应关系,而到了具体该如何实现是更为重要的话题。

为了便于计算的存储和处理,现在我们要把哪些纯数学数字对应成有限长度的比特值了。最直观的设计当然是一个字符的码点(代码)是什么数字,我们就把这个数字转换成相应的二进制表示,例如“严”在 Unicode 中对应的数字是 0x4E25,他的二进制是100 1110 0010 0101,也就是严这个字需要两个字节进行存储。按照这种方法大部分汉字都可以用两个字节来表示了。但是还有其他语系的存在,没准儿他们所使用的字符用这种方法转换就需要 4 个字节。这样问题就来了,到底该使用几个字节表示一个字符呢?如果规定两个字节,有的字符会表示不出来,如果规定较多的字节表示一个字符,很多人又不答应,因为本来有些语言的字符两个字节处理就可以了,凭什么用更多的字节表示,多么浪费。于是作为Unicode的具体实现的UTF-8和UTF-16这样的变长编码(表示代号的字节长度是变化的)方式产生了。(UTF后边的数字代表的是码元的大小。码元(Code Unit)是指一个已编码的文本中具有最短的比特组合的单元。对于 UTF-8 来说,码元是 8 比特长;对于 UTF-16 来说,码元是 16 比特长。换一种说法就是 UTF-8 的是以一个字节为最小单位的,UTF-16 是以两个字节为最小单位的。)

所以说,UTF-8,UTF-16,UTF-32(定长编码)是Unicode的具体实现,或者说是Unicode这个编码系统的组成部分。它们有着各自的特性,但都实现了对Unicode的字符集的表示,只是表达方式不同,也正因为其表达方式不同而有着各自的优势和适用领域。其中UTF-8因为其在节省空间方面的优势常被用作网络传输格式,UTF-32作为定长编码方式方便高效处理数据,使得其在内存中程序处理优秀。

有时,因为字符编码之间的不匹配,还是会出现乱码,比如,有时候编译包含中文的Java源文件会出现“ 错误: 编码 GBK 的不可映射字符”字样,如图:

这是因为在编写源代码时该源文件储存的格式与Java编译器默认翻译的格式不一致。Java源程序需要先由Java编译器编译(翻译)为字节码(计算机能识别的二进制码)之后才能够运行。在编译的时候,如果我们没有用-encoding参数指定我们的JAVA源程序的编码格式,则javac.exe首先获得我们操作系统默认采用的编码格式,也即在编译java程序时,若我们不指定源程序文件的编码格式,JDK首先获得操作系统的file.encoding参数(它保存的就是操作系统默认的编码格式,如WIN2k,它的值为GBK),然后JDK就把我们的java源程序从file.encoding编码格式转化为JAVA内部默认的UNICODE格式放入内存中。然后,javac把转换后的unicode格式的文件进行编译成.class类文件,此时.class文件是UNICODE编码的,它暂放在内存中,紧接着,JDK将此以UNICODE编码的编译后的class文件保存到我们的操作系统中形成我们见到的.class文件。对我们来说,我们最终获得的.class文件是内容以UNICODE编码格式保存的类文件,它内部包含我们源程序中的中文字符串,只不过此时它己经由file.encoding格式转化为UNICODE格式了。当我们不加设置就编译时,相当于使用了参数:javac -encoding gbk XX.java,当然就会出现不兼容的情况。

所以说,要避免这种错误,首先要知道Java源文件的由文本到储存于内存中的编码格式,比如,当该源文件以UTF-8的格式编码为二进制并存储在内存中的时候,需要告诉Java编译器编码格式为UTF-8,这样当它就能够给源文件正确解码:

详见:警告:编码GBK的不可映射字符(使用notepad++编辑器)_Java_马兆娟 廊坊师范学院信息技术提高班第八期-CSDN博客

从这个案例中可以一窥编码的本质,编码是为了方便计算机处理文字,计算机将编码后的文本储存在内存中,要想将文本重新展现出来或是转为其他格式的二进制编码,需要先知道文本的编码格式,也就是说,要先懂得编码方式,才能够正确解码。

6.总结

总之,各种编码伴随着计算机的发展而来,而现今常见的各种编码之所以还存在于计算机之中,是因为其具有其不可替代的优势或是暂时还没有更好的替代方案。

遥想自20世纪50年代计算机诞生以来,人们逐步开发计算机的潜能,为了借助计算机来辅助工作、解决各种文本上的问题而制定各种编码,因为计算机本质上是在处理数字而将各种字符编码为数字,所谓“数字化”,事实上,不仅是文字经历了数字化,声音、图像、视频等等信息都经过了数字化,计算机的计算能力掀起了数字化浪潮,给我们带来了前所未有的变化。

参考:

insight_python/Unicode_and_Character_Sets.md at master · acmerfight/insight_python

字符编码笔记:ASCII,Unicode 和 UTF-8 - 阮一峰的网络日志

警告:编码GBK的不可映射字符(使用notepad++编辑器)_Java_马兆娟 廊坊师范学院信息技术提高班第八期-CSDN博客

编码 - 维基百科,自由的百科全书

字符编码 - 维基百科,自由的百科全书

Unicode - 维基百科,自由的百科全书

等其他维基百科相关条目及《计算机科学概论》部分内容。

延伸阅读:

从原理上搞懂编码——究竟什么是编码?什么是解码?什么是字节流?_Java_云卷云舒的架构师之路-CSDN博客

字符集和字符编码(Charset & Encoding) - 吴秦 - 博客园

猜你喜欢

转载自www.cnblogs.com/Higurashi-kagome/p/12579164.html