编程的过程当中,不免会碰到一些关于编码的问题,其中对于使用简体中文的中国人来说最常见的就是 UTF-8 和 GBK 。
大部分的时候都能够使用语言内置的转换函数或者一些库来进行编码的检测和转码,但是当出现一些乱码的时候,往往有些不知所措,不知道为什么会出现这样的码点,也不知如何去解决它。这主要是由于对编码方式的极度不了解。但如果知道不同的格式是如何编码的,程序是如何解析的,那么乱码的问题便不是问题。
下面会对 ASCII, EASCII, ANSI, EUC, GB2312, GBK, UNICODE, UTF-8 和与之相关的一些术语进行解释,相关的编码方式进行阐述,从根本上解决让人头疼的编码问题。
ASCII
ASCII - American Standard Code for Information Interchange
古老(1967)的编码方式,也是最简单的编码方式,使用 7 bits 囊括了26个基本拉丁字母、阿拉伯数字、常用英文标点符合和彼时常用的控制字符,但现在大部分的ASCII控制字符都已经被废弃使用。
简单的编码会让编码解码变得简单,但是缺点也显而易见——它无法满足其他语言的编码需求。
下图是 ISO/IEC 646 定义的标准的 ASCII 7 bits 码表,其允许各国对除了英文字母和数字的其他部分修改,以满足本国需求。
EASCII
EASCII - Extended American Standard Code for Information Interchange
EASCII 顾名思义,其在ASCII的基础上拓展了1 bit,总共达到了8 bits,可以多显示128个字符。
EASCII 编码方式比较混乱,在不同的平台和协议上经常不相同,但我们主要接触到的有两种:ISO/IEC 8859-1 (Latin-1),Code page 437。
在这里有必要补充说明一下什么是代码页(Code page)。
代码页是windows上命令行中的编码表,不同的代码页标识着不同的编码方式,下面是其中一些代码页编号对应的编码方式:
437: OEM United States
1252: ANSI Latin 1; Western European (Windows)
65001: Unicode (UTF-8)
936: ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312); GBK
...
其中代码页1252与 ISO/IEC 8859-1 (Latin-1)基本一致,以下两图分别为代码页1252和代码页437:
Windows全部的代码页对应表可以查看 https://msdn.microsoft.com/en...
ANSI
ANSI - American National Standards Institute
这个名称的来历,是一个非常长的故事。我们现在说的,经常看到的一般都是Window的记事本另存为里面的ANSI编码。
它确实是ANSI这个机构所发布的 8-bit 编码标准(虽然基本上是抄 ISO 8859)。但是微软使用编码标准的时候没有注意到其实ANSI是一套标准,仅仅将它表示Latin-1。
后来逐步的,Windows的国际版本越来越多。现在,Windows上面的ANSI编码代表的是操作系统本地的默认代码页,例如在中文版Windows10上就指的是GBK。
许多文章说 GBK、BIG-5 是属于ANSI编码的,其实不对。
EUC
EUC - Extended Unix Code
EUC 是一个符合 ISO 2022 标准的 8-bit 的编码方法。
EUC 定义了4个码集:
CS0:ASCII 7 bits编码标准,即 ISO 646;一个字节
CS1: MSB(最高有效位)必须为1,0xA0-0xFF;任意字节数
CS2: MSB必须为1,第一个字节必须为0x8E,0xA0-0xFF;任意字节数
CS3: MSB必须为1,第一个字节必须为0x8F,0xA0-0xFF;任意字节数
CS1,CS2,CS3规定范围在0xA0-0xFF其实是为了兼容 ISO 2022
每个码集中都要满足如下条件:
码集中每个字符都需要使用 相同的字节数
码集中每个字符在等宽终端中都显示相同的宽度
也就是说,我决定将某些字符用CS1中的码来编码,那么这些落在CS1中的字符必须全部是一个、两个、三个、... 、N个字节,不允许在同一个码集的字符有些是一个字节,有些又是其他数量的字节。
GB2312, GBK
GB - 国标
扩(展)
GB2312又称GB0,是EUC的一种实现,它只使用了CS1,并且规定CS1为两个字节。
上面已经详细的介绍了EUC,那么GB2312的编码方式也非常明朗了。遇到MSB为0的,则读取一个字节;而遇到MSB为1的,则读取两个字节。
抛开具体的字节编码,来看GB2312的设计思路:
它一共分为81个区:
01-09:特殊符号
16-55:一级汉字(常用汉字),拼音排序
56-87:二级汉字(非常用汉字),部首/笔画排序
其中10-15,88-94区未有编码,每个区(从1开始编号)有94(区号 + 0xA0)个字;所以第一个汉字的编码就是(0xA0 + 16) + (0xA0 + 1) = 0xB0A1
关于GBK就不解释太多,只需要知道GBK是GB2312的拓展,向后兼容。
Unicode
Unicode - 一种能够囊括所有字符的编码标准
Unicode分为(0-16)平面+16位编码空间,大部分的常用语言的常用字符都落在0平面内,也就是0x0000-0xFFFF。
为了向前兼容,Unicode的前256个字符为上面说的ISO标准 8-bit 标准(ISO 8859-1)
0 平面也称为基本多文种平面(BMP),各个平面的用途如下:
ʕ•̀ω•́ʔ✧ 就是这么简单,不比上面各个编码如此复杂
UTF-8
UTF-8 - 8-bit Unicode Transformation Format
既然已经有了编码标准了,接下来就要考虑的就是怎么样把它存到计算机里了。
如果严格的按照Unicode的标准(UTF-32)来存储的话,平面编号占两个字节,编码空间占两个字节,那么不管是只需要7bits的ASCII码抑或是可以用两个字节来表示的常用中文,就都得占据四个字节,未免也太浪费(内存、储存空间和带宽资源等)了。
UTF-8是一种变长储存的编码实现方式,对于不同的字符用1~6个字节来表示。
因为是变长,所以计算机如何区分一个字符是几个字节呢?规则如下:
ASCII码范围(0x00-0x7F),使用一个字节表示
超过以上范围的,第一个字节的连续前多少位是1就表示这个字符使用几个字节,例如第一个字节为
0b1110xxxx
就表示包括这个字节在内的,接下来三个字节表示此字符。
其实上面这张图我认为将后面6列放到最前面比较合适,但是摆到后面比较好看
可以看到,上面的 x
代表的是有效储存位,其他的都是用来起标识作用的。那么我们的中文到底占几个字节呢?
查表可知,常用汉字的Unicode编码落在基本多文种平面的0x4E00-0x9FFF(中日韩统一表意文字),即三个字节。
认真的人看到这里肯定会有一个疑问,为什么多字节字符除了第一个字节以外,后面的字节还是要以0b10
开头呢?
让我们把目光放到上图,可以看到第一个字节没有以0b10开头的。如果后面的字节使用了全部的空间,程序每次定位一个位置都要从头开始查找,因为程序并不知道当前这个字节是否是第一个字节;而如果使用0b10作为开头的话,程序只需要向前查找不是0b10
开头的字节,就可以知道它是当前字符的起始字节。
BOM
BOM - Byte-order Mark
由于UTF-16和UTF-32是定长编码,所以在编码单元里面存在大端和小端储存的问题,所以制定了以下规则来标识文件:
虽然UTF-8这种变长的编码方式不存在大端小端的问题,但是还是指定了BOM来表示这个文件是UTF-8。在实际的使用过程当中尽量不要对UTF-8添加BOM,否则很多软件解析都会出现问题。
之后遇到文件前有两个或者三个不知道是什么的字节的时候就不用惊慌了。
编码检测
一直以来都对 notepad++ 等一干智能编辑器如何自动检测文件编码非常好奇,趁此机会查了下资料。
基本上所有的编码检测都不是完全可靠的,都是根据字节的模式统计分析来确定最可能的编码,so~
其中能被最可靠的探测到的编码就是UTF-8了,根据上文,UTF-8有大量的0b10xxxxxx
以及首字节的定式编码,所以一般来说不是很容易被检测错误。
✧⁺⸜(●˙▾˙●)⸝⁺✧ 写了一个月的文章完结撒花 ✧⁺⸜(●˙▾˙●)⸝⁺✧