基本概念

比特(bit)
  也可以称为“位”,是计算机信息中的最小单位,binary digit(二进制数位)的缩写,指的是二进制中的一位。
字节(Byte)
  8位(bit)构成一个字节(Byte)
字符(Character)
  文字和符号的总称,可以是各个国家的文字、标点符号、图形符号、数字等
字符集(Character Set)
  为什么要有字符集?在计算机屏幕上是实体化的文字,而在计算机存储介质中存放的实际是二进制比特流,那么两者之间的转换规则就需要一个统一标准。为了实现转换标准,各种字符集标准就此出现。 简单来说,字符集规定了某个文字对应的二进制存放方式(编码)和某二进制数值串代表了哪个文字(解码)的转换关系。如ASCII、utf-8等就是字符集。
字符编码与解码
  文字到0、1的映射称为编码,反过来从0、1到文字叫解码。

ASCII

  1964年,世界上第一天计算机诞生,设计者用8个晶体管的“通”或“断”组合出一些状态来表达信息。8个晶体管的“通”或“断”即可以代表一个字节,也就是8个比特(bit)表示一个字节(byte)。计算机只能处理数字,如果处理文本,必须先把文本转为数字才能处理。
一个字节能表示的最大的整数就是255。把一些终端的动作、字母、数字和符号用8位(bit)来组合。

  • 0000 0000 ~ 0001 1111 共 33 种状态用来表示终端的特殊动作,如打印机中的响铃为 0000 0111 ,当打印机遇到 0000 0111 这样的字节传过来时,打印机就开始响铃;
  • 0010 0000 ~ 0010 1111 、 0011 1010~0110 0000 和 0111 1101 ~ 0111 1110 共 33 种状态来表示英式标点符号,如 0011 1111 即代表英式问号“?”;
  • 0011 0000 ~ 0011 1001 共 10 种状态来表示“0~9”10个阿拉伯数字;
  • 0100 0001 ~ 0101 1010 和 0110 0001 ~ 0111 1010共 52种状态来表示大小写英文字母;

  一共只用到了128种状态,即128个字符,刚好占用了一个字节中的后7位,共包括33个控制字符和95个可显示字符,
  这一字符集被称为ASCII(American Standard Code for Information Interchange,美国信息交换标准代码),这一套字符集在1967年被正式公布。

位数:ASCII是用7位表示的,能表示128个字符;其扩展使用8位表示,表示256个字符。范围:ASCII从00到7F,扩展从00到FF。

GB2312

  《信息交换用汉字编码字符集》是由中国国家标准总局1980年发布,1981年5月1日开始实施的一套国家标准,标准号是GB 2312—1980。
  GB 2312或GB 2312-80编码适用于汉字处理、汉字通信等系统之间的信息交换。供包含7445个字符,6763个汉字和其他682个字符。
  GB 2312对其所收录的字符进行了“分区”处理,共94个分区,从1到94,每个区有94个位,位从1到91,共8836(94*94)个码位,这种表示方法称为区位码。
GB 2312是双字节码,其中高字节码表示区,低字节表示位。各区具体说明如下:

1
2
3
4
5
01-09 区收除汉字外的682个字符,有164个空位(9*94-682)
10-15 区为空白区,没有使用
16-55 区收录3755个一级汉字(简体),按拼音排序
56-87 区收录3008个二级汉字(简体),按部首/笔画排序
88-94 区位空白区,没有使用

  如何根据区位码计算GB 2312编码了?区位码表示范围为0101-9494(包含的空的区位码)。点击这里查看GB 2312编码区位码。之后按照如下规则转换即可。

  1. 将区(十进制)转为十六进制
  2. 将转化的十六进制加上A0,得到GB 2312编码的高字节
  3. 将位(十进制)转化为十六进制
  4. 将转化的十六进制加上A0,得到GB 2312编码的低字节
  5. 组合区和位,区在高字节,位在低字节
  6. 得到GB 2312

  例如:’李’字的区位码为3278(表示在32区,78位)。1. 将32(区)转化为十六进制为20。2. 加上A0为C0。3. 将78(位)转化为十六进制为4E。4. 加上A0为EE。5. 组合区和位,为C0EE。6. 得到GB2312编码,即’李’字的GB2312编码为C0EE。

Unicode

  为了统一所有文字的编码,Unicode应运而生。Unicode编码定义了几乎所有字符的数字表示,而且Unicode还兼容了很多老版本的编码规范,如ASCII编码。
我们国家每一个人都对应一个唯一的身份证号码,而Unicode也为每个字符发了一张身份证,这张“身份证”上有一串唯一的数字ID确定了这个字符。
  这串数字在整个计算机的世界具有唯一性,这串数字ID叫码点
  码点经过映射后得到的二进制的转换格式单位称之为码元(Code Unit)码点就是一串二进制数,码元就是切分这个二进制数的方法。
  举例,如果有一个字符的码点二进制表示有n字节(n*8个二进制数),其码元为8位(1个字节),那么其拥有码元n个。

utf-8就是每读码点8位代表一个字符,utf-16就是每读码点16位代表一个字符

  Unicode编码发展到今天扩展到了21位,因为一开始老美只考虑那26个英文字母和数字,随着越来越多的国家的语言编码。Unicode不得不继续扩展,目前21位已足够使用。
UTF-32的码元是32位,每32位去读一下码点,而码点是Unicode给字符的编码,上文提到最长才21位,因此每一个UTF-32值都可以直接表示对应的码点。
什么是编码空间?
  前面提到的Unicode是21位的,21位提供了1,114,112个码点,编码空间就是对应这1,114,112个码点。这么多码点并不代表这么多字符,目前大概只有10%的空间被使用了,人类社会还没创造出1,114,112这么多字符。
  编码空间被分成17个平面,从00 - 10(十六进制,最高两位),即从0 - 16(十进制),每个平面有65,536个字符(正好填充两个字节,16位)。0号平面叫做基本多文种平面(BMP,Basic Multilingual Plane),码位从0000 - FFFF,涵盖了几乎所有你能遇到的字符,除了emoji(emoji位于1号平面)。其他平面叫做补充平面,大多是空的。

Unicode只是一个符号集,只规定的字符所对应的码点,并没有指定如何存储,如何进行存储出现了不同的编码方案,关于Unicode编码方案主要有两条主线:UCS和UTF。UTF主线由Unicode Consortium进行维护管理,UCS主线由ISO/IEC进行维护管理。

UTF

  UTF全称为”Unicode Transformation Format”,在UTF中主要有UTF-8,UTF-16和UTF-32。

UTF-16

  UTF-16的码元是16位,即吗,每16位去读一下码点,获取码点前16位数字,直到读取完成。
BMP平面中的每一个码点都直接与一个UTF-16的码元一一映射。
由于BMP 几乎包括了所有常见字符,UTF-16一般需要 UTF-32大约一半的空间。至于其它平面里很少使用的码点都是用两个16位的码元来编码的。
也就是说UTF-16对于常见字符使用2个字节,不常用的字符使用4个字节,大大节省了空间。

UTF-8

  UTF-8使用1到4个字节来编码一个码点,从0到127的这些码点直接映射成一个字节(多余只包含这个范围字符的文本来说,这一点使得UTF-8完全兼容ASCII,对于ASCII中的字符,UTF-8采用的编码值跟ASCII完全一致)。接下来1,920个码点映射成2个字节,在BMP里所有剩下的码点需要3个字节。Unicode的其他平面里的码点则需要4个字节。UTF-8是基于8位的码元的,因此它不需要关心字节顺序(因为字节就是8位,其它UTF-16和UTF-32在不同的机器编译环境下需要考虑字节的顺序问题)。
  因此,有效率的空间使用,以及不需要关心字节顺序问题使得UTF-8成为存储和交流Unicode文本方面的最佳编码。
  Unicode码点转为UTF-8编码规则如下:

  1. 对于单字节字符,字节第一位设为0,后面7位位这个福海的Unicode码。因此,对于英语字母,UTF-8编码和ASCII码是相同的
  2. 对于n字节的字符(n>1),第一个字节的前n位设为1,第n+1位设为0,后面字节一律设为10.剩下的二进制位位这个字符的Unicode码。

总的编码规则如下:

1
2
3
4
5
6
7
Unicode符号范围                   |   UTF-8编码方式
     (十六进制) (十进制)     |   (二进制)
  ----------------------------------------------------------------------------------------------------
0000-0000 007F (0-127) |    0xxxxxxx
0080-0000 07FF (128-2047) |    110xxxxx 10xxxxxx
0800-0000 FFFF (2048-65535) |   1110xxxx 10xxxxxx 10xxxxxx
0000-0010 FFFF (65536-1114111) |    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

  字符’A’的Unicode码点为65(十进制),根据上表,在第一行范围,则字符’A’的UTF-8编码为01000001,中文字符’李’的Unicode码点为26446(十进制),二进制为01100111 01001110,十六进制为674E。根据上表,在第三行范围,则将’李’二进制代码从低位到高位依次填入x中,不足的填入0。得到UTF-8编码为11100110 10011101 10001110,即E69D8E(十六进制)。
  由上述编码规则可知,0000 0000 - 0000 FFFF(第一行到第三行)为Unicode第一个平面(基本多语言平面),而0001 0000 - 10 FFFF(第四行)为Unicode其他平面(辅助平面)。在基本多语言平面对应了绝大多数常用的字符。对于大于65535(十进制)的码点,即在辅助平面上的码点,需要使用4个字节来进行UTF-8编码。

JVM中使用的是UTF-16

UCS

  UCS全称为”Universal Character Set”,在UCS中主要有UCS-2和UCS-4。
UCS-2
  UCS-2是定长字节的,固定使用2个字节进行编码,从0000(十六进制)- FFFF(十六进制)的码位范围,对应第一个Unicode平面。采用BOM(Byte Order Mark)机制,该机制作用如下:1. 确定字节流采用的是大端序还是小端序。2. 确定字节流的Unicode编码方案。
UCS-4
  UCS-4是定长字节的,固定使用4个字节进行编码。也采用了BOM机制。

Emoji

  Emoji急速一种在Unicode位于\u1F601-\u1F64F区段的字符,这个显然超过了目前最常用的UTF-8字符集的编码范围\u0000-\uFFFF
  将Emoji存储与MySQL时可能存在一些问题。一般而言MySQL默认字符集都会配置成UTF-8(三字节),而utf8mb4在5.5以后才被支持。那么当把一个需要4字节UTF-8编码才能表示的字符存入MySQL时就会报错:ERROR 1366: Incorrect string value: '\xF0\x9D\x8C\x86' for column。也就是试图将一串Bytes插入到一列中,而这串Bytes的第一个字节是\xF0意味着这是一个四字节的UTF-8编码。但当MySQL字符集配置为UTF-8时就无法存储这样的字符而报错。
  遇到此问题有以下两种方案:

  1. 审计MySQL到5.6或更高的版本,并将表字符集切换为utf8mb4
  2. 把内容存入数据库之前做一次过滤,将Emoji字符替换成一段特殊的文字编码,再存入数据库。之后数据库获取或前端展示时再将这段特殊文字编码转换成Emoji显示。

Python中的编码

  Python2中默认的字符编码是ASCII码,python3默认使用unicode编码

前端编码

  告知浏览器自己的文件采用了什么编码,有以下几种常见方法:

1
2
3
4
<meta charset="gb2312"> //html5
<meta http-equiv="Content-Type" content="text/html; charset=gb2312"> //html4 xhtml
<script src="http://ossweb-img.qq.com/images/js/foot.js" charset="gb2312"></script>
<link href="http://gameweb-img.qq.com/css/common.css" rel="stylesheet" charset="gb2312" >

  可以在head区域的meta元素中为整个页面申明编码方式,也可以为单独的外链文件申明编码方式(link/script等元素)。如果页面头部和外链文件中只有部分申明或者全部申明,那么对应的到底是以什么方式解码呢?这里有一个优先级的问题

  通过上面可以发现,一个页面中优先级最高的其实是服务端的编码设置,如果一旦服务端设置了编码A,那么页面即以A来解析。 目前Google采用的是这一做法,这样的传输效率会更高,不需要在头部额外再单独申明编码,但这样其实也有一定的风险,除了需要有一个严谨的编码规范,还需要确保服务器上的页面都保持同一编码,一旦不一致就会造成乱码。其他的,如果外链资源设置了编码C,那么即以C来解析,无论服务端和头部是否申明编码。 另外需要注意的一点是:申明的编码只是告诉浏览器相关的内容是以什么方案去解码,并不是这一部分内容就采用了这个编码。

MIME编码

  MIME 是“多用途网际邮件扩充协议”的缩写,在 MIME 协议之前,邮件的编码曾经有过 UUENCODE 等编码方式 ,但是由于 MIME 协议算法简单,并且易于扩展,现在已经成为邮件编码方式的主流,不仅是用来传输 8 bit 的字符,也可以用来传送二进制的文件 ,如邮件附件中的图像、音频等信息,而且扩展了很多基于MIME 的应用。从编码方式来说,MIME 定义了两种编码方法Base64与QP(Quote-Printable)

Base64

Base64编码要求把3个8位字节(38=24)转化为4个6位的字节(46=24),之后在6位的前面补两个0,形成8位一个字节的形式。

QP(Quote-Printable)

通常缩写为“Q”方法,其原理是把一个 8 bit 的字符用两个16进制数值表示,然后在前面加“=”。所以我们看到经过QP编码后的文件通常是这个样子:=B3=C2=BF=A1=C7=E5=A3=AC=C4=FA=BA=C3=A3=A1。

总结:

  1. 为了处理英文字符,产生了ASCII码
  2. 为了处理中文字符,产生了GB2312
  3. 为了处理各国字符,产生了Unicode
  4. 为了提高Unicode存储和传输性能,产生了UTF-8,它是Unicode的一种实现形式。

参考:
https://www.itcodemonkey.com/article/12576.html
https://www.cnblogs.com/leesf456/p/5317574.html
http://djt.qq.com/article/view/658?ADTAG=email.InnerAD.weekly.20130902&bsh_bid=281085951
https://apps.timwhitlock.info/emoji/tables/unicode
https://www.cnblogs.com/cenalulu/p/4251639.html
https://www.cnblogs.com/zhangqigao/p/6496172.htm