一篇读懂字符编码基本概念
1 字符编码
1.1 什么是字符编码
众所周知,在计算机的世界中,不管是存储还是传输都是通过二进制(单位:比特,bit)来表示数据的,而我们人类能理解的文字、标点符号、特殊符号、emoji表情等字符只有被映射为二进制才能被计算机所识别,而这个映射则被称之为字符编码(Character Encoding,以下简称编码),反之称为解码。
1.2 现代编码模型
关于编码涉及的概念非常多,Unicode Technical Report #17给出的字符编码模型主要包括抽象字符库、编码字符集、字符编码模式、字符编码方案四个层次。尽管是unicode出的编码模型,对于套用在一些其他的编码上依然适用(比如ISO8859-1、GBK等)。
(1)抽象字符库(Abstract character repertoire)
抽象字符库是一个系统支持的所有抽象字符的集合。通常情况下可以简单地理解为是一大堆字母、数字、控制字符(如空格、回车等)、标点符号等字符组成的集合。这里的字符是“抽象”的,与字形是不一样的,比如“A”字符,用微软雅黑是长这样,用宋体又是另外一个样了。
(2)编码字符集(CCS:Coded Character Set)
如果将抽象字符库中每一个字符都映射成非负整数(对)的话(如Unicode“A”-> 65,GB2312的 '啊' -> (16,01)),那么这些映射的集合就称之为编码字符集(也有直接称字符集)。用于映射的非负整数的范围,则被称之为编码空间(encoding space),编码空间中的一个位置(position)称为码位(code point),例如在ASCII编码字符集中,字符 'A' 的码位值为65。编码字符集也可以理解成把抽象字符映射为码位值。简单理解就是字符与整数的映射。
另外,编码空间可以用一个整数来描述,例如:ISO-8859-1的编码空间是256;可以用一对整数来描述,例如:GB2312的汉字编码空间是94 x 94;也可以用字符的存储单元尺寸来描述,例如:ISO-8859-1是一个8-bit的编码空间;还可以用其子集来表述,如行、列、面(plane)等。但不管如何描述,字符在编码空间内都有对应的码位值(一个非负整数)。
(3)字符编码模式(CEF:Character Encoding Form)
非负整数确定后,这时候就需要把它映射成二进制的序列了。这个映射往往不能简单地映射,例如整数 2->[10] 8->[1000],因为很明显,当解码[1000] 时,到底是 [1000] 还是 [10] + [00]呢?但是假如我定好了4-bit是一个整体(基本单元),这时 2->[0010] 8->[1000],就很好分辨了。这里所说以个整体就是字符编码模式我们常说的码元(code unit),表示用于处理或交换编码文本时的基本单元,码元的长度是确定的,比如这里的4-bit、ISO 8859-1的8-bit、UTF-8的8-bit、UTF-16的16-bit等。
字符编码模式(CEF,也有翻译为字符编码表),也称为"storage format",规定了从CCS 中的非负整数到一组特定码元序列的映射。码元序列由一个多或多个码元组成。
根据码元组成的序列长度是否可变,CEF可以区分为两大类:
- 固定宽度 (fixed width) 编码模式:字符由一个码元确定,码元序列就是CCS中整数的对应码元长度的二进制(不够则前面补0),如UCS-2、UCS-4、UTF-32等
- 可变宽度 (variable width) 编码模式:码元的数量可以有多个,如UTF-8、UTF-16等
我们常说的GBK编码、UTF-8/16编码等,通常指的就是这一层干的活。
(4)字符编码方案(CES:Character Encoding Scheme)
字符编码方案(CES),也称作"serialization format",将一个个CEF中的码元映射到字节(8-bit)序列,以便编码后的数据的文件存储或网络传输。这时侯时真正将二进制序列放入到硬件中了。
在Unicode的相关编码方案中,当一个字符对应的码元序列为多个字节时,根据字节存储的顺序可以分为大端序(big-endian)和小端序(little-endian)。例如字符'我'在UTF-16中对应的码元序列为[<u>01100010</u> 00010001],当采取大端序存储时,码元序列的高字节放在存储器的低地址中,存储器从低地址到高地址顺序读取时,读取到的第一个字节为[<u>01100010</u>],第二个为[00010001];而采用小端序时,则反过来了,数据的低字节放在低地址中,顺序读取时第一个字节为[00010001],然后再是[<u>01100010</u>]。
为了告诉计算机是到底是大端序还是小端序,还需要在编码后的字节流的开头指定一个字节顺序标记(byte-order mark,BOM),0x<u>FE</u>FF表示大端,0xFF<u>FE</u>表示小端。
2.编码的历史
字符编码的历史,其实就是一个在不断制定和更新标准的过程。范围从英文到欧洲字符、汉字乃至全世界的文字和符号。
2.1 初出茅庐ASCII
上世纪60年代,美国制定了一套基于拉丁字母的编码标准,即ASCII(American Standard Code for Information Interchange,美国信息交换标准代码),ASCII字符集共收录了95个显示字符(英文字母、数字、一般的符号)和33个控制字符,共128个字符,至少7bit来表达所有字符,即000 0000-111 1111,十进制为0-127,例如'A' 对应的码位值为65。因为ASCII的普及范围广,后来的编码字符集一般都会对ASCII做兼容。
值得一提的是,“字节”最初的长度表示用于编码单个字符所需要的比特数量,曾基于硬件为1-48比特不等,而直到二十世纪70年代开始,1字节才被标准化为8比特(某硬件的流行?)。正是因为这样,现代的字符编码(UTF-8、GBK等),码元的长度通常为8的整数倍。ASCII用单个字节存储的时,最开始最高位用于校验,后来则用作了ASCII编码拓展。
2.2 欧洲发展EASCII与ISO 8859
伴随着欧洲强国对计算机的引入和发展,各个厂商或者是国家为了满足自己的需求,均推出了自己的一套EASCII编码(Extended ASCII,延伸美国标准信息交换码),其中比较出名的有IBM PC的Code page 437。注意:EASCII指的是对ASCII空置的 [1000 0000-1111 1111] 进行拓展的那一类编码,而不是特指哪一个。正因为这样,当时的编码格局显得有些混乱。
为了统一ASCII的相关拓展编码,国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位元(单字节)字符集的标准,定义了共15个字符集。分别是:
- ISO/IEC 8859-1 (Latin-1) - 西欧语言
- ISO/IEC 8859-2 (Latin-2) - 中欧语言
- ISO/IEC 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。
- ISO/IEC 8859-4 (Latin-4) - 北欧语言
- ISO/IEC 8859-5 (Cyrillic) - 斯拉夫语言
- ISO/IEC 8859-6 (Arabic) - 阿拉伯语
- ISO/IEC 8859-7 (Greek) - 希腊语
- ISO/IEC 8859-8 (Hebrew) - 希伯来语(视觉顺序)
- ISO 8859-8-I - 希伯来语(逻辑顺序)
- ISO/IEC 8859-9(Latin-5 或 Turkish)- 它把Latin-1的冰岛语字母换走,加入土耳其语字母
- ISO/IEC 8859-10(Latin-6 或 Nordic)- 北日耳曼语支,用来代替Latin-4
- ISO/IEC 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来
- ISO/IEC 8859-13(Latin-7 或 Baltic Rim)- 波罗的语族
- ISO/IEC 8859-14(Latin-8 或 Celtic)- 凯尔特语族
- ISO/IEC 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的芬兰语字母和大写法语重音字母,以及欧元(€)符号
- ISO/IEC 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号
这时欧洲几乎所有国家的字符都有其对应的编码标准了(尽管你只能选择其中一个)。
加入了96个西欧字母及符号的ISO 8859-1(Latin-1),因为标准化早且覆盖区域广,成为了欧美十分流行的编码,mysql、tomcat等软件的一些早期版本默认的编码就是ISO-8859-1。
套于编码模型上,EASCII和ISO 8859编码空间均为8bit,码元长度8-bit。因为是定长、单字节存储,编码十分简单。如 'A' -> 码位值为 [110 0101] ,码元序列 8-bit 固定长度,前面补个0得:[0110 0101],实际存储没那么多花哨的东西,直接存储单个字节 [0110 0101] 即可。
2.3 中国崛起GB2312与GBK
GB2312起源
就如最初的欧洲一般,我国也是没有自己文字的编码,直到1980年,中国国家标准总局发布中华人民共和国国家标准简体中文字符集,即GB 2312(GB即"国标"拼音首声母)。GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。为了装下这几千个字符,GB2312字符均用双字节编码。
分区
GB2312编码对字符进行了“分区”处理,共94个区,每个区有94个汉字/符号。区号和位号组成一个区位码(码位):
- 01~09区(682个):特殊符号、数字、英文字符、制表符等,包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母等在内的682个全角字符;
- 16-55区(3755个):一级汉字,按拼音排序。
- 56-87区(3008个):二级汉字,按部首/笔画排序。
- 10-15区、88-94区:空区,留待扩展。
比如 '啊' 是GB2312编码字符集中的第一个一级汉字,区位码为(16, 01),十六进制为:0x1001,二进制:[00010110 00000001]。
国标码
GB2312字符集中并没有定义控制字符,而是沿用了ASCII中的那32个控制字符,而这时候就出现了个问题了,如果直接使用,比如"啊"的区位码作为二进制序列的话为:[00010000 00000001],而 [00010000] 在ASCII表示DLE(跳出数据通讯),这时候软件解析时就不知道是取单个字节的DLE还是双字节的"啊"了。
GB2312采用的办法是将字符对应的区位码均偏移32(每个字节大小加上32、即0x20),解析时,只要是小于等于32的字节则认为是ASCII控制字符,否则为GB2312的双字节字符。这个区位码+32的码就是我们常说的“国标码/交换码”,比如'啊'的国标码=0x1001+0x2020=0x3021。
机内码
国标码发布之后,我们又发现了一个问题。尽管GB2312定义了英文字母和数字,但是这些都是和汉字等大的字母和数字(全角),如果有一篇文章的字母既有全角又有半角的话,国标码就不能满足了。国标码只是解决了控制字符的冲突问题,却没有解决其他ASCII字符冲突问题。
后来微软在搞Windows中文版本时,索性在国标码的基础上,最高位都取1,这时候就和ASCII就都兼容了,这个这就是我们常说的“机内码”。机内码=国标码+0x8080 [1000 0000]=区位码+0x2020 + 0x8080=区位码+0xA0A0,例如 '啊' 的机内码=0x3021+0x8080=0x1001+0xA0A0=0xB0A1。当字节小于128 [1000 0000]时,直接取单个字节解析为一个ASCII字符;大于等于128的,取两个字节组成一个GB2312字符。例如“A啊”在存储器中的十六进制为“41 B0 A1”,共三个字节。这种储存方法也被称之为EUC-CN(Extended Unix Code CN,一个使用8位编码来表示字符的方法,主要是为了兼容ASCII)。94区机内码的范围为0xA1-0xFE。
GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。然而对于人名、古汉语等方面出现的罕用字和繁体字,GB 2312并不支持,直到后来的GBK出现,这些问题才得以解决。
GBK
1995年12月,中华人民共和国全国信息技术标准化技术委员发布了汉字内码扩展规范,即GBK(K为"扩展"拼音第一个声母),GBK共收录21886个汉字和图形符号,其中汉字21003个,图形符号883个。
GBK是完全兼容GB2312编码的(例如'啊'机内码均是'B0A1'),而拓展的可能:
(1)GB2312原本未编码的10-15、88-94区;
(2)当初取最高位1时(+[1000 0000]),是在国标码的基础上的,事实上,我们可以直接绕过国标码这个过时的玩意(或者说绕过EUC-CN的存储方式),直接在区位码最高位取1(区位码+[1000 0000],当然原来的国标码最高位取1依然需要兼容),原本0xA1作为机内码起始点,现在可以从0x81开始了(0x81-0xFE共126项)。而当第一个字节确定非ASCII字符时,第二个字节则没必要从0x80[1000 0000]开始了。这时候就有足够的编码空间了。
除了汉字,还有韩文、日文等均有自己国家的编码标准(通常是双字节编码),这时候似乎又回到了欧洲编码EASCII混乱的格局了。
2.4 一统江湖Unicode
unicode起源
为了统一全世界字符编码,解决各种编码的相互冲突的问题,20世纪80年代末,在电脑普及和资讯国际化的前提下,一些商业机构以及国际标准化组织(ISO)分别成立了Unicode组织和ISO-10646工作小组。他们不久便发现对方的存在,大家为着相同的目的而工作。1991年,Unicode 组织与ISO/IEC委员会同意保持Unicode码表与ISO 10646标准保持兼容并密切协调各自标准近一步的扩展。虽然实际上两者的字集编码相同,但实质上两者确实为两个不同的标准。Unicode 1.1对应于ISO 10646-1:1993,Unicode 3.0对应于ISO 10646-1:2000,Unicode 3.2对应于ISO 10646-2:2001,Unicode 4.0对应于ISO 10646:2003,Unicode 5.0对应于ISO 10646:2003及附录1–3[1]。可以认为unicode标准下的unicode字符集和ISO-10646标准下的通用字符集(Universal Character Set, UCS)是名称不同但是实际内容差不多的东西。
unicode字符集,通常指的是编码模型中的第一二层,就是收录字符(囊括了英文、欧洲字符、中文、日文、韩文等)以及每个字符都赋予一个对应的非负整数,每个字符可以命名为U+{对应非负整数的十六进制},如“我”-> U+6211。另外emoji等表情符号在unicode中也有对应的码位。
unicode字符集依然兼容ascii,即在ascii中的那128个字符对应的整数与unicode中是一致的,比如“A”都是65。
基本平面与辅助平面
如果说GBK字符集用分区来表示编码空间的话,那么unicode字符集则可以叫“分面”了。unicode的编码空间划分为17个平面(plane),每个平面包含65,536(2^16)个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从00到10(十进制就是0-16),共计17个平面。其中字符使用频率最高的是第一个平面,即基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0),其他平面称为辅助平面(Supplementary Planes)。基本多语言平面内,从U+D800到U+DFFF之间的码位区段是永久保留不映射到Unicode字符,UTF-16就利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码(详见下文)。
UCS-2和UTF-16
UCS-2表示通用字符集下的一种编码模式实现方式,把UCS中的码位映射成码元长度为2字节(16-bit)的码元序列,UCS-2是一个定长的编码,即码元个数永远都是1个(2字节)。
UTF-16(Unicode Transformation Format, unicode转换格式),表示Unicode字符集下的一种编码模式实现方式,把unicode字符集种的码位映射成成码元长度为16-bit(2字节)的码元序列,UTF-16是一个变长的编码,即单个字符对应的码元个数可以为1或2,即最终的序列长度可以为2字节或4字节。
最初的unicode字符集/UCS编码空间并不大,unicode到了3.0版本时(1999年9月)也就49,259个字符,双字节共216=65,536项,用于存储这个字符集绰绰有余。而因为UCS和unicode字符集的对应关系,UTF-16可看成是UCS-2的父集。在unicode编码空间尚未突破216之前(即辅助平面还没出来之前),UTF-16与UCS-2可以认为是同一个编码。
最初可能大家都捧着“65535 个字符足够全世界用了”的观念,为许多编程语言(如C/java等)和流行软件(如Mysql)留下了不少的坑。
伴随着unicode字符集的扩展,自unicode3.1版本开始(2001年3月),编码空间突破65,536,双字节已经存不下了。这时候UTF-16顺利成章地用上了两个码元(即2字节*2=4字节)。而UCS-2则只能对UCS/unicode字符集较前的部分字符进行编码。后来为了编码所有的字符,ISO-100646推出了UCS-4编码(等同于unicode的UTF-32),即码元长度为4字节/32-bit(定长)。
UTF-16 基本平面编码
在基本平面中,即U+0000至U+FFFF中,除开U+D800~U+DFFF这段无映射字符的区间,其他均被直接映射到单个码元(16-bit)的序列。例如“我”-> U+6211,被映射为二进制序列 [0110 0010 0001 0001]
UTF-16 辅助平面编码
为了区分字符是由单个码元还是两个码元表示,对于辅助平面上的编码,UTF-16使用两个U+D800~U+DFFF区间的码元表示一个字符。
以“𤭢”(U+24B62)为例,说说码位到码元序列的映射过程:
- 0x24B62-0x10000=0x14B62,结果二进制为5个4位共20位:[0001 0100 10<u>11 0110 0010</u>]
- 将这20位分成两个10位,即0001 0100 10 和 11 0110 0010,十六进制分别为0x52和0x362
- 前10位+0xD800=0x52+0xD800=0xD852,作为序列中的第一个码元(高位);
- 后10位+0xDC00=0x362+0xDC00=0xDF62,作为序列中的第一个码元(低位);
- 两个码元组成了最终的一对码元序列,即 “D852 DF62”,也就是代理对(surrogate pair),高位代理为前导代理(lead surrogates),低位代理为后尾代理(trail surrogates)。
解码时反着来就行。
UTF-8
UTF-16的码元长度为16-bit(两个字节),一个字符可以由一到两个码元组成;
UTF-8的码元长度则为8-bit(一个字节),一个字符可以由一至六个码元组成,具体字符与字节的关系如下:
- 128个ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
- 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要两个字节编码(Unicode范围由U+0080至U+07FF)。
- 基本平面中的其余的字符(如大部分的汉字)使用三个字节编码(Unicode范围由U+0800至U+FFFF)。
- 其他极少使用的Unicode 辅助平面的字元使用四至六个字节编码(Unicode范围由U+10000至U+1FFFFF使用四字节,Unicode范围由U+200000至U+3FFFFFF使用五字节,Unicode范围由U+4000000至U+7FFFFFFF使用六字节)。
UTF-8的优势主要在于一个是和UTF-16一样,可以编码所有的unicode字符;二是ASCII字符仅需要占用一个字节,对于以英文数字为主的数据可以省50%的空间(相对于UTF-16)。但是对于汉字字符来说,会比GBK多50%的大小(不过GBK对于一些特殊字符,例如目前流行的emoji表情符号就不支持了)。
UTF-8 编码规则
UTF-8会根据第一个字节的“情况”来区分多少个码元(字节)表示一个字符。具体的规则如下:
(1)单个字节,首字节最高位为0,剩余的7-bit为可编码位数,unicode的0x00-0x7F的码位可以映射到这里。
(2)多个字节,首字节最高位开始,连续的二进制位值为1的个数就是其编码的字节数,其余各字节均以10开头。未被指定的其他位会对Unicode码位进行编码映射(如下表的"x")
可编码位数 | 码位范围 | 字节数量 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 |
---|---|---|---|---|---|---|---|---|
7 | U+0000~U+007F | 1 | 0xxx xxxx | |||||
11 | U+0080~U+07FF | 2 | 110x xxxx | 10xx xxxx | ||||
16 | U+0800~U+FFFF | 3 | 1110 xxxx | 10xx xxxx | 10xx xxxx | |||
21 | U+10000~U+1FFFFF | 4 | 1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx | ||
26 | U+200000~U+3FFFFFF | 5 | 1111 10xx | 10xx xxxx | 10xx xxxx | 10xx xxxx | 10xx xxxx | |
32 | U+4000000~U+7FFFFFFF | 6 | 1111 10xx | 10xx xxxx | 10xx xxxx | 10xx xxxx | 10xx xxxx | 10xx xxxx |
例如,“我”-> U+6211,二进制为[0110 0010 0001 0001],处于三字节编码处,将其填充至对应的"x"位置即可:[1110 0110 1000 1000 1001 0001],对应的十六进制序列为 "E6 88 91"共三个字节。
UTF-8并不像UTF-16那样有区分大小端序,BOM(字节顺序标记)有无并不会影响字符对应的二进制序列。UTF-8中,无论有BOM和无BOM,“我”都是对应“E6 88 91”,只不过有BOM在前面多加个标记符号(“EF BB BF”)而已。
2.5关于Base64与Url编码
Base64编码、Url编码和UTF-8/GBK这些不属于一个编码层次。前者是把UTF-8/GBK编码后的字节序列,再进行一次映射,形成新的二进制序列,以满足传输环境的限制(见下文)。
Base64
早期email的传输和解析只需要满足英文就行了,也没考虑说其他一些复杂的东西。邮箱相关的一些协议(如SMTP),往往是基于纯ASCII文本的(ASCII编码),传输过程中对于二进制文件或者GB2312/UTF-8等编码后的二进制序列并不支持。如果你非要发这种图片、视频、GB2312编码后的中文等,这就得先把原来的二进制序列转换为ASCII字符(ASCII编码)对应的二进制序列,这样才能保证传输过程中不会出现问题(比如GB2312的首字节被ASCII取7-bit解析为终止字符等)
Base64是一种基于64个可打印字符来表示二进制数据的表示方法,这64个字符分别是:A-Z,a-z,0-9,+,/。原本若干个字节的二进制序列,每6-bit作为一个整体(2^6=64),映射到对应的可打印字符中。其映射关系为:【0-25 -> 'A'-'Z'】【26-51 -> 'a'-'z'】【52-61 -> '0'-'9'】【62 -> '+'】【63 -> '/'】,映射后的字符也就可以使用ASCII编码了。
假如有个三字节序列:[0000 1111 0000 1111 0000 1111] 进行Base64编码:
(1)以6-bit进行切分得到:[0000 11] [11 0000] [1111 00] [00 1111],即 3、48、60、15;
(2)找到对应的可打印字符:D、w、8、P
(3)对字符进行ASCII编码,得新的二进制序列:[0100 0100] [0111 0111] [0011 1000] [0101 0000]共四个字节
原本3字节24-bit的数据,现在变成了 24/6=4个字节大小了,Base64编码后传输大小为原来的4/3了;
如果原数据的字节数不能被3整除(位数不能被6整除),需要用0补足字节,补上的全0映射至'='字符。例如:
(1)单字节 [0000 1111],补充到三字节[0000 1111] [0000 0000][0000 0000],切分成 [0000 11] [11 0000][0000 0000] [0000 0000],即 映射成 'D'、'w'、'='、'='。
(1)双字节 [0000 1111 0000 1111],补充到三字节[0000 1111 0000 1111][0000 0000],切分成 [0000 11] [11 0000][1111 00] [00 0000],即 映射成 'D'、'w'、'8'、'='。
这也是为什么我们经常能看到Base64字符串会以'='结尾的原因。
Base64目前在Web中的应用场景通常是对于图片的编码,例如图片的展示,或者上传图片时可以直接连同其他数据一起提交至服务器,服务器以接收普通字符一样接收图片。
Url编码
Url编码(URL encoding),也称百分号编码(Percent-encoding),是指特定上下文的统一资源定位符 (URL)的编码机制。URI所允许的字符分为保留字符:【!*'();:@&=+$,/?#[]】和非保留字符:【=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~】。保留字符是有特殊含义的,例如https://wikipedia.org中的'/'用于URL不同部分的分界符。
对于除了非保留字符之外的字符,均要使用百分号编码(即由若干个 '%XX'组成),其中保留字符对应的百分号编码为:
字符 | 百分号编码 | 字符 | 百分号编码 |
---|---|---|---|
! | %21 | + | %2B |
# | %23 | , | %2C |
$ | %24 | / | %2F |
& | %26 | : | %3A |
' | %27 | ; | %3B |
( | %28 | = | %3D |
) | %29 | ? | %3F |
* | %2A | @ | %40 |
[ | %5B | ] | %5D |
其他的例如汉字,则是进行指定的编码后(如UTF-8、GBK编码),再转换为百分号编码。例如'我',UTF-8编码后为二进制序列十六进制为“E6 88 91”,对应的%编码即为 “%E6%88%91”。
用上百分号编码的地方:
- url的 path,如https://zh.wikipedia.org/wiki/我,实际上请求为:https://zh.wikipedia.org/wiki/%E6%88%91;
- application/x-www-form-urlencoded类型数据请求。GET请求时,如https://www.baidu.com/s?wd=我,实际为:https://www.baidu.com/s?wd=%E6%88%91;POST请求时,在请求体中的数据也会被浏览器进行URL编码。
不同的浏览器,不同的页面,可能进行的编码(可能为UTF-8或者为GBK),可以参考这篇文章,注意:处理实际问题时,不同浏览器或版本可能不一样,具体问题具体分析。
3 编码选择
这么多种编码用哪个?我的建议是:对于一般业务数据的存储和传输,UTF-8一把梭就完事了;对于我国环境下,GBK仍然是个不错的选择,前提是容忍部分字符的缺失(如emoji,会出现乱码);在一些需要定长的场景下(例如后来的一些编程语言的字符),可以选择UTF-32/UCS-4。
值得注意的是,各类编码本身的定义是一回事,软件是否支持又是另外一回事了,一定要结合具体的场景讨论编码问题。比如Mysql 5.5.3之前的版本的utf8编码最大只支持3个字节的字符编码,emoji都存不了。直到后面出了个utf8mb4才支持到最大四个字节。