乱码问题&编码解码

2018-12-10 07:51
176
0

为什么会出现乱码呢?有两种可能的原因,一种是选择了错误的编码,一种是选择了错误的解码。

字符集&各种编码&编码解码

要理解乱码问题,首先需要理解几个概念:字符集、编码、编码规则

1. 字符集

字符(Character)是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。字符集(Character set)是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集名称:ASCII字符集、ANSI(GB2312字符集、BIG5字符集、 Shift_JIS字符集等)、Unicode字符集等。其实字符集简单了来说,就是一张表格,是 id 和字符的对应表。

其中ANSI是一种编码方案,ANSI代表了不同国家的不同编码方案,如果一台Windows操作系统设定为中文,那么ANSI就表示GBK,如果设定为日本ANSI就表示Shift_JIS。

2. 各种编码:

一种编码格式必须选定一个字符集。比如 UTF-8和 UTF-16 / UTF-32 选用 Unicode 字符集,GB2312选用GB2312字符集(ANSI),编码格式是一种保存到计算机中(硬盘、内存)。

编码的历史

1. ASCII

目的及背景: 计算机起源于美国,早期的计算机只是用于科学计算,但是在计算机迅速发展时,计算机被要求不仅仅能够进行数值计算,还要进行字符处理和表示。于是一套名为ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)码的编码方式被创造而出。
实现形式: 使用8位(bit)-一个字节来表示一个字符,总共能够表示256种字符,即2⁸。
对应内容: 这套编码包含了控制码(例如n换行符等)、符号(?!等)、数字、拉丁字母、特殊符号和26个英文字母包括其大小写。

2. ANSI

目的及背景: 当计算机普及到全世界时,各个国家面临的首要问题就是要针对自己国家的语言制定一套自己国家的编码规范。
实现形式: 以我国为例,我国就提出里一套针对中文的GB2312的编码方式,这套编码方式基于ASCII码(并非IBM的ASCII扩充版本),使用2个字节表示一个汉字,具体的方式是前127个字符不变。当第一个字节(高字节)大于160的时候,表示一个汉字的开始,再用这个字节组合第二个字节(低字节,范围也是160-255)共同表示一个汉字。在这套编码方式中,不仅把中文编码进去,还把一些数学符号、罗马希腊字母和日本假名等等都编码进去,并且还把ASCII中原有的26个英文字母和符号都编入,当然这些字母是以2个字节表示,为和ASCII中原有的字母区别表示,称前者为“全角字符”,后者为“半角字符”。当然我大中华文化底蕴深厚,GB2312也只能编入部分常用汉字,为了把更多的汉字编入进来,针对GB2312进行扩充,就创造出了GBK标准。GBK只要求当高字节大于127时就表示汉字的开始,低字节也不再要求范围。
对应内容: GB2312表示 (255-160)(255-160)=9025个汉字及其他字符,扩充后的GBK标准表示 (255-127)256=32768个汉字及其他字符

类似我国的编码方案,其他地区和国家也制定了自己的编码方案,如日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里等等。这些编码方案称为 "DBCS"(Double Byte Charecter Set 双字节字符集)"即是用双字节表示一个字符,也称为ANSI。这里的ANSI代表了不同国家的不同编码方案,如果一台Windows操作系统设定为中文,那么ANSI就表示GBK,如果设定为日本ANSI就表示Shift_JIS。ANSI虽然能够表示全世界的字符,但是产生两个比较麻烦的问题:
1、数量统计:一段使用ANSI编码的字符串,有中文有英文,如何统计共有多少字符?这下麻烦了,如果是单纯的英文,sizeof一下即可,但是有中文混杂其中则必须便利一遍并做判断,十分麻烦
2、编码转换:也是最麻烦的一个问题,使用ANSI编码的一篇文章,被台湾友人拿去,打开后,全是乱码,因为台湾地区使用的是BIG5码,不同的地区编码虽然都称作ANSI,但是互相之间没有算法做出转换,这大大影响了阻碍各地区、国家之间的交流

3. Unicode

目的及背景: 为了统一全世界的文字编码,ISO(国际标准化组织)制定了一种新的编码规范,这种编码规范将全世界的文字放在一张表内,称它为"Universal Multiple-Octet Coded Character Set",简称 UCS, 俗称"UNICODE"。
实现形式: 严格意义上说Unicode只是一套标准,为全世界文字给予一个唯一的编码,最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。
对应内容: 全世界的文字

关系

现在,捋一捋ASCII编码和Unicode编码的区别:ASCII编码是1个字节,而Unicode编码通常是2个字节。

字母A用ASCII编码是十进制的65,二进制的01000001
字符0用ASCII编码是十进制的48,二进制的00110000,注意字符'0'和整数0是不同的;
汉字已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01001110 00101101
你可以猜测,如果把ASCII编码的A用Unicode编码,只需要在前面补0就可以,因此,A的Unicode编码是00000000 01000001

新的问题又出现了:如果统一成Unicode编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。
所以,本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间:

不同字符集的编码、解码规则:

1.UTF-8的编码规则:
UTF-8是一种变长字节编码方式。对于某一个字符的UTF-8编码,如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的位数,其余各字节均以10开头。UTF-8最多可用到6个字节。

字节数  表现形式
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

因此UTF-8中可以用来表示字符编码的实际位数最多有31位,即上表中x所表示的位。除去那些控制位(每字节开头的10等),这些x表示的位与UNICODE编码是一一对应的,位高低顺序也相同。
实际将UNICODE转换为UTF-8编码时应先去除高位0,然后根据所剩编码的位数决定所需最小的UTF-8编码位数。
因此那些基本ASCII字符集中的字符(UNICODE兼容ASCII)只需要一个字节的UTF-8编码(7个二进制位)便可以表示。

2.UTF-16的编码规则:
UTF-16是Unicode字符集的一种转换方式,即把Unicode的码位转换为16比特(使用2个字节(16位)或4个字节来存储其编号)长的码元串行,以用于数据存储或传递。

为什么是2个或4个字节表示呢?因为如果要包含全世界所有的符号文字,即使使用2个字节都无法包含,想想GBK编码,占满了2个字节仅仅包含我们大中华文字部分文字。但是针对各国的常用文字在2个字节内都能包含,所以通常使用2个字节的UTF16就足够了。为什么是2个或4个字节表示呢?因为如果要包含全世界所有的符号文字,即使使用2个字节都无法包含,想想GBK编码,占满了2个字节仅仅包含我们大中华文字部分文字。但是针对各国的常用文字在2个字节内都能包含,所以通常使用2个字节的UTF16就足够了。

下面是一个实例:

字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
/ 01001110 00101101 11100100 10111000 10101101

从上面的表格还可以发现,UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。

在计算机中是如何运作的

搞清楚了ASCII、Unicode和UTF-8的关系,我们就可以总结一下现在计算机系统通用的字符编码工作方式:

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件,
浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器,
所以你看到很多网页的源码上会有类似<meta charset="UTF-8" />的信息,表示该网页正是用的UTF-8编码。

附带:BOM标记

一篇文章存储在内存中,使用记事本等工具打开时,记事本是怎么知道这篇文章使用什么编码方式呢?就是靠的位于文章最开头的BOM标记

前面说了要知道具体是哪种编码方式,需要判断文本开头的标志,下面是所有编码对应的开头标志(大端小端一般常见于网络传输中)

BOM标识 编码方式
EF BB BF UTF-8
FE FF UTF-16/UCS-2,little endian
FF FE UTF-16/UCS-2,big endian
FF FE 00 00 UTF-32/UCS-4,little endian.
00 00 FE FF UTF-32/UCS-4,big-endian.
 

发表评论