字符集和编码
前言
最近几天在分析一个字符乱码问题,想知道对方(恶意用户 或者说是黑客)到底想做什么。最后发现对方使用了Unicode字符集进行编码,而我们日志收集端使用的是utf-8编码解码,这就导致了乱码。
不知道大家注意到没上面这段文字中出现了Unicode“字符集”和utf-8“编码”,可能很多朋友都什么是字符集、什么是编码有些分不清。今天就来详细的聊下。
我们都知道电脑只能识别二进制数0和1,我们在显示器上看到的所有符号都可以由多个0和1进行表示。每个二进制位叫做bit,8个bit称为1个字节。每个符号(字符)可以由1个或多个字节表示,换句话说字节是表示符号的最小单位。但二进制不方便人类阅读和查看,一般还会引入十进制和十六进制。
前面提到的“符号”可以简单理解为“电脑”里的一个图标,可以是任意的文字、标点符号、或者声音等等,比如“A”、“中”、“国”。这里的每个“符号”对应了前面提到二进制表示的数字,这里我们暂且称为“符号码”, “电脑”中的每个“符号”都对应了一个“符号码”(一一对应)。无论是硬盘存储还是网络传输数据时,都是用的“符号码”,只是在最终显示的时候“电脑”会根据这个“符号码”查找到对应的“符号”用于展示即可。
那什么是字符集呢,简单的理解就是:字符集=“符号”+“符号码”。我们在安装linux系统时,通常会安装一些字符集,此时就会把对应字符集里定义的“符号”、以及“符号码”对应的码表安装到电脑中。每个字符集又有自己的编码规则,下面分别来看下字符集以及其对应的编码。
字符集
世界各国为了自己国家语言符号的需要,定义了很多字符集。这里只列出常见的几种:ASCII
、ISO8859-1、GB2312、GBK、Unicode(注意这些才是所谓的字符集)。
ASCII
美国人在发明计算机时,只考虑了本国语言,他们在定义“符号”时,只考虑了英语:字母、数字、标点符号等;一共只定义了128个“符号”,对应的“符号码”是从0到127(当然是二进制的)。这就是ASCII码,全称是American Standard Code for Information Interchange,从这个全称上也可以看出只是包含了美国人语言习惯的字符(英语)。
前面提到过表示一个字符(文中提到的字符和“符号”是一个意思)的最小单位是“字节”, ASCII只定义了128个“符号”, 1个字节的低7位就足够了(2的7次方),第一位补0即可。可以看出ASCII是单字节表示。
ISO-8859-1
欧洲一些国家除了使用ASCII中定义的符号外,还有有自己的语言符号。ASCII中不是还有1位没用么,于是他们把这位也利用起来,一共可以表示256个字符(2的8次方)。
ISO-8859-1完全兼容ASCII(反过来却不行,第一个bit位会丢失),也是单字节表示。
GB2312
中文的符号比较特殊,既没有在ASCII中定义 也没有在ISO-8859-1中定义,也就是说刚开始的电脑中是无法显示中文的。由于汉字比较特殊,每个汉字都是一个单独的符号,汉字的个数又多,所以1个字节肯定是不够用的。
GB2312是中国人自己定义的中文字符集,采用两个字节表示。它收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。其中收录汉字6763个,其他文字符号682个。
GBK是对GB2312的扩展,可以表示21003个汉字。换句话说GBK完全兼容GB2312(反之则不行)。
Unicode
每个国家都去定义自己的字符集显然不是一种通用的做法,而且也不便于世界各国的信息交流。假如:中国的电脑上只安装了GB2312字符集,就无法显示用泰语写的文案。有个叫ISO的组织创建了一个全新的字符集Unicode,这个字符中定了世界上所有语言中的符号。另外,unicode字符集的编号兼容了ASCII字符集的编号和ISO 8859-1字符集的编号。
Unicode 目前规划的总空间是17个平面(平面0至16),每个平面有 65536 个码点。
第0个平面的取值范围:0 0000-0 FFFF
第1个平面的取值范围:1 0000-1 FFFF
…
第16个平面的取值范围:F 0000-F FFFF
可以看到Unicode编码需要20个bit位来进行存储。一般而已对应第0个平面 只需要2个字节进行存储;其他平台需要4个字节。在后面的Unicode具体编码规则中 ,又分为多种情况。
编码
编码,或者说字符集的编码 一般的全称为xxx字符集的编码。简单的理解编码就是使用多少个字节以什么样的格式来存储码值。
首先来看最简单的ASCII,没有特殊的编码规则,使用一个字节的低7位存储即可,首位补0。
ISO-8859-1,也没有特殊的编码规则,使用一个字节的全部8为进行存储,不过多的进行解读。
GB2312的编码规则,稍微复杂些。由于GB2312没有定义ASCII中的字符,在遇到英文符号时还是采用单字节的ASCII码;如果是中文字符时,采用双字节进行存储。GB2312中有分区的概念,一共94个区(01-94),每个区定义了94个位置(01-94);分区和分区中的位置都用一个字节表示,此时如果不处理的话,每个字节都跟ASCII码重叠了。GB2312的编码规则是把分区号和位置值都加160,原来的取值范围是0-94,现在变为160-254(一个字节不能超过256,这也是为什么要加160的原因)。
简单的理解GB2312的编码规则:非ASCII字符 使用两个字节进行存储,每个字节的取值范围是160-254;如果某个字节的数值小于128,说明是ASCII字符,这个字符直接使用ASCII进行解析。
GBK字符集的编码规则大致与GB2312类似,只是进行了扩展。
最后重点说下Unicode字符集的编码规则,因为只有这个字符集才能做到全球通用。Unicode字符集的编码方式有多种:UTF-8、UTF-16、UTF-32。注意utf是编码,Unicode是字符集 这就是文章的主题:编码和字符集的区别。只是ASCII、ISO-8859-1、GB2312这些字符集的编码规则只有一种,所以有时候既可以说是字符集 又有人说是编码;但Unicode字符集 有多种编码规则,为什么要设置多个编码规则呢?
UTF-16
首先来看UTF的全称:Unicode Transformation Format 其实就是“Unicode字符集的转换格式”。
相信很多人都看到过java中的char使用的Unicode是两个字节,但不要错误的认为Unicode
只采用了两个字节。Unicode 目前规划的总空间是17个平面(平面0至16),每个平面有 65536 个码点,我们常说的两个字节,只是最常用的平面0中定义的字符(又称BMP Basic Multilingual Plane),这时UTF-16编码规则只需要两个字节存储。
另外16个平面 即平面1到16 ,UTF-16编码规则是采用的4个字节进行存储,一共32个bit位。具体格式如下:`110110pp ppxxxxxx 110111xx xxxxxxxx`,其中pp pp这4个字节用于表示16个平面(2的4次方);另外16个x用于表示每个平面中的位置(2的16次方 即65536个位置)。
简单理解UTF-16编码规则:Unicode的第0个平面中的字符用2个字节存储,其他16个平面都是使用的4个字节存储。地球人常用的字符都在第0个平面里面,这个平面只需要2个字节,也比较节省空间。
但有些情况下采用两个字节存储也很浪费空间,比如前面提到的ASCII中的字符,原本只需要1个字节的,现在都需要两个字节(高位字节每个bit都为0)。在存储英文相关字符时,存储空间翻倍,这就产生了UTF-8编码。
UTF-8
UTF-8采用变长技术实现对Unicode字符集的编码,可以采用1-6个字节用来表示Unicode字符集。UTF-8编码规则:如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的字节数,其余各字节均以10开头。UTF-8转换表表示如下:
Unicode |
bit数 |
UTF-8 |
字节数 |
备注 |
0000 ~ 007F |
0~7 |
0XXX XXXX |
1 |
|
0080 ~ 07FF |
8~11 |
110X XXXX 10XX XXXX |
2 |
|
0800 ~ FFFF |
12~16 |
1110XXXX 10XX XXXX 10XX XXXX |
3 |
第0个平面中的字符,也称BMP |
1 0000 ~ 1F FFFF |
17~21 |
1111 0XXX 10XX XXXX 10XX XXXX 10XX XXXX |
4 |
其他16个平面中的字符(Unicode定义了20个bit位,这里有21个可以用位,已可以完全覆盖Unicode字符集) |
20 0000 ~ 3FF FFFF |
22~26 |
1111 10XX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX |
5 |
已超出Unicode的范围 |
400 0000 ~ 7FFF FFFF |
27~31 |
1111 110X 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX |
6 |
从这个图可以看出在UTF-8中,最多使用4个字节就可以覆盖所有的Unicode字符集。另外由于UTF-8使用了变长技术,在大多数情况下比UTF-16更节省空间。
UTF-32
UTF-32编码是一种暴力方式,直接使用4个字节存储Unicode字符集,Unicode字符集的最大值是10FFFF。也就是说使用4个字节来存储Unicode字符集,表示范围如下(16进制):
00 00 00 00 00 ~ 00 10 FF FF。
可以看到至少有1个高位字节全部为0,这是最浪费空间的一种编码方式。也就是最简单的Unicode字符集编码方式。
最后再说下UTF-8、16、32后面这个数字,其含义为最少使用多少个bit位,UTF-8表示最少使用8bit位 即一个字节;UTF-16最少使用16个bit位,2个字节;UTF-32固定使用32个bit位。
总结
最后简单总结下,字符集和编码是有区别的,常见的字符集有ASCII、ISO8859-1、GB2312、GBK、Unicode。ASCII、ISO8859-1、GB2312、GBK这些字符集只有一种编码规则,所以有时候即被称为字符集又被称为编码;Unicode字符集有三种常见的编码:utf-8、utf-16、utf-32。
我们经常遇到的乱码问题,本质就上就是在字符转换成字节流(二进制的字符集编码)是采用了一种编码方式,字节流转换成字符时又使用了另一种编码方式(解码),最终出现乱码。当然不是说所有使用不同的编码进行编解码都会出现乱码,兼容的情况就不会出现乱码:比如用IOS-8859-1解ASCII,用GBK解GB2312,用utf-8解ASCII都不会出现乱码。
字符集中定义的“字符码”值(code point),与编码后的码值 有可能是不相同的。ASCII和IOS-8859-1是相同的,而GB2312和Unicode字符集定义的code point值与编码后的值是不同的。它们的关系如下:
另外字符集和编码也是密码学的基础,基本都是在二进制的编码上进行一些位运算,当然这个计算过程是个复杂是很复杂的。
如果还要更深入的了解字符集和编码想的内容可以参考这一系列文章:https://zhuanlan.zhihu.com/paogenjiudi
上一篇: Unity游戏项目中后期如何做海外版翻译
下一篇: 内嵌式--代码启动OSGI框架