谈谈计算机中的编码问题

在介绍这个问题之前先扯一下计算机编码的发展史。

有没有同学想过1个字节为什么等于8个比特(1B=8b)?为什么一个英文字母占用1字节,而一个汉字占用两字节(然而实际要考虑是什么编码)?

如果处理足够的数据以使计算变得可行,那么更少的位就不起作用,而太多的位会导致昂贵的硬件。——摘自software enginering评论

字节,原意就是用来表示一个完整的字符的。最原始的时候“计算机”(或许当时还不能称为计算机)完全是以二进制来计算工作的,当时是没有字节的概念的。字节其实就是编码发展的必然,然而历史为什么会折中选择8位?从最早的打孔I/O到后来的键盘显示器,我们处理的数据也在不断发展:数字、控制符、符号、英文字符、多国语言字符。最初的计算机性能和存储容量都比较差,所以普遍采用4位BCD编码(这个编码出现比计算机还早,最早是用在打孔卡上的)。BCD编码表示数字还可以,但表示字母或符号就很不好用,需要用多个编码来表示。后来又演变出6位的BCD编码(BCDIC),以及至今仍在广泛使用的7位ASCII编码。不过最终决定字节大小的,是大名鼎鼎的System/360(当时的大型计算机)。

当时IBM为System/360设计了一套8位EBCDIC编码,涵盖了数字、大小写字母和大部分常用符号,同时又兼容广泛用于打孔卡的6位BCDIC编码。System/360很成功,也奠定了字符存储单位采用8位长度的基础,IBM于1956年首先为Stretch项目推出了8位寻址,接下来的大多数主流电脑主要基于原来的IBM PC,这就是现在1字节=8位的由来。其实字节的大小在历史上与硬件有关,并且不存在规定大小的确定性标准 - 过去已知使用1到48位的字节大小。只是现在说的字节普遍等于8bit。

个人理解:计算机最初诞生于美国,计算机不断的从1位.2位….的发展,直到ASCII美国信息交换标准代码定义了一个7位字符集并成为了当时最流行的编码方式,但由于当时硬件的发展,提倡用2的幂来处理任何事情,人们并不介意在7位ascii码的基础上再加上一位,并把这八位叫作字节(后来这多加的一位被用于奇偶校验)。当时这8位可以容纳 - 26个小写字母+ 26个大写字母+ 10个数字+大量的标点符号和特殊符号。这已经足够好几年了!所以之后的发展都是基于这8bit的操作,但随着计算机全球化,256(2^8)个字符已经不够用于表达其他语言的符号…….继而发展出了各种各样的编码方式,但由于习惯…都是基于byte的基础上进行发展。 为何1byte=8bit?这一切都是因为硬件的发展和人类对于2的情有独钟而导致的,或许如果在当时发明了不同于晶体管的电子器件,处理器的设计构架不同,设计师的世界观改变….就会有完全不一样的结果了吧。

Tip:现代计算机的32位或64位指的是字长,即同一时间内处理的一组二进制数称为一个计算机的字,而这组二进制数的位数就是“字长”。和字节没有关系!


这个历史的发展最终以8位胜出为结果。等到我们中国人使用计算机的时候已经没有多余的字节状态来表示汉字,何况有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国 人民,我们不客气地把那些127号之后的扩展字符集直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。 (一个汉字算两个字节的缘由)

1980年,我们把这种汉字编码方案叫做“GB2312”。GB2312是ASCII的中文扩展。

但这仅仅满足了常用汉字的录入,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符 集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。再到后来又增加了几千个少数名族的字,GBK扩成 GB18030。

进而全世界有上百种语言,日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里,各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。当时做多语言软件的公司可以说是非常难受。

因此, ISO (国际标谁化组织)的国际组织决定着手解决这个问题,Unicode字符集应运而生。Unicode把所有语言都统一到一套字符集里,这样就不会再有乱码问题了。Unicode标准也在不断发展,但最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。由于”半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种方案在保存英文文本时会多浪费一倍的空间。现代操作系统和大多数编程语言都直接支持Unicode。

接下里我们结合实例来详细介绍一下常用编码。


一、目前常用的编码

编码这个问题一直对新手很不友好,我也是深受困扰才写了这么一篇文章。特别在网页这一块。如果你码的代码不是乱码,而网页中出现了乱码,绝大部分原因就出在了编码上了。在程序设计中,编码问题也是尤为重要。

编码方面的一些概念

大端(big endian)和小端(little endian)

字节序

Unicode码可以采用UCS-2格式直接存储。以汉字”严“为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。

这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。

因此,第一个字节在前,就是”大头方式“(Big endian),第二个字节在前就是”小头方式“(Little endian)。

那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?

Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格“(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。

如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。我们一般将endian翻译成“字节序”。

BOM

当使用类似 WINDOWS 自带的记事本等软件,在保存一个以UTF-8编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即BOM)。它是一串隐藏的字符,用于让记事本等编辑器识别这个文件是否以UTF-8编码。这样就可以避免这个问题了。对于一般的文件,这样并不会产生什么麻烦。

这样做,也有弊处,尤其体现在网页中。PHP并不会忽略BOM,所以在读取、包含或者引用这些文件时,会把BOM作为该文件开头正文 的一部分。根据嵌入式语言的特点,这串字符将被直接执行(显示)出来。由此造成即使页面的 top padding 设置为0,也无法让整个网页紧贴浏览器顶部,因为在html一开头有这3个字符。如果你在网页中,发现了由未知的空白等,很有可能就是由于文件有 BOM 头造成的。遇到这种问题,把文件保存的时候,不要带有 BOM 头!

UTF

UTF(Universal Transformation Format,通用传输格式),其实就是不改变字符集中各个字符的代码,建立一套新的编码方式,把字符的代码通过这个编码方式映射成传输时的编码,最主要的任务就是在使用Unicode字符集保持通用性的同时节约流量和硬盘空间。主要分为UTF-8,UTF-16,UTF-32三种。其中最受欢迎的是UTF-8,UTF-8大受欢迎的主要原因就是对欧美而言,在保留Unicode通用性的情况下避免了流量和空间的浪费。因此广泛运用在互联网,统计2016年互联网87%的网页是用UTF-8编码的。

UCS

UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的。UCS有两种不同的规定版本:UCS-2和UCS-4。顾名思义,UCS-2就是用2个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。因此,UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。目前UCS-2已经足够用了,UCS-4估计都可以把Asgard和氪星的文字(传说都是英语?)也收录进来了……具体的符号对应表,可以查询http://unicode.org,或者相关的字符对应表。


二、windows记事本

接下来我们先从windows记事本的四种常用编码来说——部分内容摘自知乎记事本

  • 所谓的「ANSI」指的是对应当前系统 locale(地区) 的遗留(legacy)编码。[1]
  • 所谓的「Unicode」指的是带有 BOM 的小端序 UTF-16。[2]
  • 所谓的「UTF-8」指的是带 BOM 的 UTF-8。[3]

[1] Windows 里说的「ANSI」其实是 Windows code pages,这个模式根据当前 locale 选定具体的编码,比如简中 locale 下是 GBK。把自己这些 code page 称作「ANSI」是 Windows 的臭毛病。在 ASCII 范围内它们应该是和 ASCII 一致的。
[2] 把带有 BOM 的小端序 UTF-16 称作「Unicode」也是 Windows 的臭毛病。Windows 从 Windows 2000 开始就已经支持 surrogate pair 了,所以已经是 UTF-16 了,「UCS-2」这个说法已经不合适了。UCS-2 只能编码 BMP 范围内的字符,从 1996 年起就在 Unicode/ISO 标准中被 UTF-16 取代了(UTF-16 通过蛋疼的 surrogate pair 来编码超出 BMP 的字符)。
[3] 把带 BOM 的 UTF-8 称作「UTF-8」又是 Windows 的臭毛病。如果忽略 BOM,那么在 ASCII 范围内与 ASCII 一致。

GBK 等遗留编码最麻烦,所以除非你知道自己在干什么否则不要再用了。
UTF-16 理论上其实很好,字节序也标明了,但 UTF-16 毕竟不常用。
UTF-8 本来是兼容性最好的编码但 Windows的记事本偏要加 BOM 于是经常出问题。

1.打开”记事本“程序,新建一个文本文件,内容就是一个”严“字,依次采用ANSI,Unicode,Unicode big endian 和 UTF-8编码方式保存。

然后,用文本编辑软件UltraEdit中的”十六进制功能“,观察该文件的内部编码方式。

1)ANSI:文件的编码就是两个字节“D1 CF”,这正是“严”的GB2312编码,这也暗示GB2312是采用大头方式存储的。

2)Unicode:编码是四个字节“FF FE 25 4E”,其中“FF FE”表明是小头方式存储,真正的编码是4E25。

3)Unicode big endian:编码是四个字节“FE FF 4E 25”,其中“FE FF”表明是大头方式存储。

4)UTF-8:编码是六个字节“EF BB BF E4 B8 A5”,前三个字节“EF BB BF”表示这是UTF-8编码,后三个“E4B8A5”就是“严”的具体编码,它的存储顺序与编码顺序是一致的。

2.当你在 windows 的记事本里新建一个文件,输入”联通”两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!

1522922967841

因为记事本默认保存的编码方式是ANSI,在我国也就对应着gb2312,但是由于联通这两个字的ANSI编码符合UTF-8编码的第二个模板,记事本当再次打开这个文件的时候,就会误以为是UTF-8编码的文件,

就是因为编码和解码使用的码表不同,导致了”联通”乱码的问题


三、总结

一、把字符转换成字节的过程称为编码,将字节转换成字符的过程称为解码。如果编码和解码使用的码表不一致,就会导致乱码问题。

二、平时尽量避免记事本编程(带BOM而且转换方式奇葩),特别是网页开发。

三、如果你是为了跨平台,那么最有效的办法确实是坚持使用 UTF-8。关于用带不带BOM的utf-8,推荐without BOM。微软在 UTF-8 中使用 BOM 是因为这样可以把 UTF-8 和 ASCII 等编码明确区分开,但这样的文件在 Windows 之外的操作系统里会带来问题。因为它不需要,于是很多跨平台的软件其实并不支持这种格式甚至语言。

四、少使用遗留编码(GBK、Big-5等等),都8102年,Unicode字符集规范都提出来多少年了。

三、使用其他文本编辑器(例如:notepad ++或 EmEdit)来查看或修改!

四、在编程方面编码还有许许多多的坑,当遇到编码问题有时候会很麻烦,所以平时应养成良好的习惯,遇到这些问题记录下来加深印象!

参考链接和推荐链接:

https://blog.csdn.net/hherima/article/details/39548551 记事本输入”联通”

http://www.bobbemer.com/BYTE.HTM “ASCII码之父” Bob Bemer——WHY IS A BYTE 8 BITS? OR IS IT?

https://softwareengineering.stackexchange.com/questions/120126/what-is-the-history-of-why-bytes-are-eight-bits What is the history of why bytes are eights bits? - Software Engineering

https://zhuanlan.zhihu.com/p/21388517 “字节序”是个什么鬼?- 知乎

https://blog.csdn.net/ldanduo/article/details/8203532 编码格式简介(ANSI、GBK、GB2312、UTF-8、GB18030和 UNICODE) - CSDN博客

https://www.zhihu.com/question/20167122 「带 BOM 的 UTF-8」和「无 BOM 的 UTF-8」有什么区别?- 知乎

https://www.zhihu.com/question/20152853 对于字符编码,程序员的话应该了解它的哪些方面?- 知乎

觉得好的话就打赏Ta一瓶冰阔落吧