Encode - 字符编码那些事

编码发展

1. ASCII - 早期阶段

ASCII(American Standard Code for Information Interchange:美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。

早只有127个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122。

2. GB2312、GBK - 各国自主编码阶段

有几万字的中文,如果处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312、GBK、GB编码,用来把中文编进去;

Shift_JIS、Euc-kr 全世界有上百种语言,日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里,各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。

GB2312

GB2312或GB2312–80,是中华人民共和国国家标准简体中文字符集,1981年5月1日实施。GB2312编码通行于中国大陆;新加坡等地也采用此编码。GB2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;

GB2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。但对于人名、古汉语等方面出现的罕用字和繁体字,GB2312不能处理,因此后来GBK

GBK

GBK只为“技术规范指导性文件”,不属于国家标准。GBK,全名为《汉字内码扩展规范(GBK)》1.0版,由中华人民共和国全国信息技术标准化技术委员会1995年12月1日制订,GBK的K为“扩展”的汉语拼音(kuòzhǎn)第一个声母。

GBK是一种编码方式并向下兼容GB2312,GBK共收录21886个汉字和图形符号,其中汉字(包括部首和构件)21003个,图形符号883个。随后,国家质量技术监督局于2000年3月17日推出了GB18030-2000标准,以取代GBK。

3. Unicode 万国码 - 全球编码标准过程

Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。

1993年,Unicode 1.1版本推出,收录中国大陆、台湾、日本及韩国通用字符集的汉字,总共有20,902个。中国大陆订定了等同于Unicode 1.1版本的“GB 13000.1-93”

4. UTF-8、UTF-16 - 基于Unicode,从存储和传输效率考虑

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。

UTF-8使用一至六个字节为每个字符编码,这些多字节的最高有效比特会设置成1(尽管如此,2003年11月UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF,也就是说最多四个字节),:

  1. 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
  2. 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要两个字节编码(Unicode范围由U+0080至U+07FF)。
  3. 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字,如大部分的汉字)使用三个字节编码(Unicode范围由U+0800至U+FFFF)。
  4. 其他极少使用的Unicode 辅助平面的字符使用四至六字节编码(Unicode范围由U+10000至U+1FFFFF使用四字节,Unicode范围由U+200000至U+3FFFFFF使用五字节,Unicode范围由U+4000000至U+7FFFFFFF使用六字节)。

5. Python中处理

// gb231找不到繁体字龍的编码
>>> '龍'.encode('gb2312')
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'gb2312' codec can't encode character '\u9f8d' in position 0: illegal multibyte sequence
>>> '\u9f8d'
'龍'
    
// gbk扩展后,有龍的编码,占用两个字节
>>> '龍'.encode('gbk')
b'\xfd\x88'
    
// utf编码,占3个字节
>>> '龍'.encode('utf-8')
b'\xe9\xbe\x8d'
>>>

其他编码相关内容

1. URL编码、百分号编码

百分号编码, 也称作URL编码, 是特定上下文的统一资源定位符 (URL)的编码机制,实际上也适用于统一资源标志符(URI)的编码;也用于为application/x-www-form-urlencoded MIME准备数据, 因为它用于通过HTTP的请求操作(request)提交HTML表单数据。 编码方式:

当前是采用RFC 3986标准,建议所有新的URI必须对未保留字符不加以百分号编码,对其他非ASCII字符, 需要转换为UTF-8字节序, 然后每个字节按照上述方式表示,比如汉字等;

  • 保留字与未保留字
    • 保留字:诸如!*'();:@&=+$,/?#[] 这类有特殊含义的字符
    • 未保留字:诸如abc-z,ABC-Z,1-9,-_.~
  • 若保留字需要特殊含义,该字符必须百分号编码;/为具有特殊含义的保留字符,如果在URI路径侧,则需要百分号编码;
  • 字符%号需要特殊编码为%25
  • 空格被编码为%20,有的则被编码成+号(有很多小的修改如新行规范化以及把空格符的编码%20替换为+

转码效果

//php执行urlencode
$ php -r 'echo urlencode("https://tkstorm.com?id=12345&keyword=中 文");'
https%3A%2F%2Ftkstorm.com%3Fid%3D12345%26keyword%3D%E4%B8%AD+%E6%96%87%
 
//python执行(quote对'/'保留字做编码,文档解释默认quote函数是在使用现有斜杠字符的路径上调用保留字符)
>>> from urllib import parse
>>> parse.quote("https://tkstorm.com?id=12345&keyword=中 文")
'https%3A//tkstorm.com%3Fid%3D12345%26keyword%3D%E4%B8%AD%20%E6%96%87'
 
//非ascii字符,先转成UTF-8编码,占3个字节的'中'字符
>>> '中'.encode("utf-8")
b'\xe4\xb8\xad'
>>> parse.quote('中')
'%E4%B8%AD'

表单提交中的编码

  • 如果发送的是HTTP GET请求, application/x-www-form-urlencoded数据包含在所请求URI的查询成分中.
  • 如果发送的是HTTP POST请求或通过email, 数据被放置在消息体中,媒体类型的名字被包含在消息的Content-Type头内部。
POST /image/ HTTP/1.1
Host: img.tkstorm.com
Content-Type: application/x-www-form-urlencoded
...

2. Base64编码

Base64是一种基于64个可打印字符来表示二进制数据的表示方法

由于2^6=64,所以每6个比特为一个单元,对应某个可打印字符的3个字节有24个比特,对应于4个Base64单元,即原来3个字节需要由4个Base64中可打印的字符来表示,故经过Base64编码后的的长度,为原数据长度的4/3,根据RFC 822规定,每76个字符,还需要加上一个回车换行。可以估算编码后数据长度大约为原长的135.1%

在Base64中的可打印字符包括字母A-Z、a-z、数字0-9,这样共有62个字符,此外两个可打印符号在不同的系统中而不同:

  • 通用MIME中:加号+斜杠/
  • 用于URL的改进Base64编码:-_,它不在末尾填充=号
  • 用于正则表达式的改进Base64变种,它将+/改成了!-
  • 等号=用来作为后缀用途

编码过程

  1. 转换的时候,将3字节的数据,先后放入一个24位的缓冲区中,先来的字节占高位。
  2. 数据不足3字节的话,于缓冲器中剩下的比特用0补足。
  3. 每次取出6比特,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/中的字符作为编码后的输出,直到全部输入数据转换完成。
  4. 若原数据长度不是3的倍数时且剩下1个输入数据,则在编码结果后加2个=;若剩下2个输入数据,则在编码结果后加1个=。

编码用途

Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,Base64它可用来作为电子邮件的传输编码、Base64图片编码等:

  • 隐私增强邮件
  • MIME:(多用途互联网邮件扩展)规范列出Base64编码为两个中的一个二进制到文本编码方案(比如邮件内嵌图片)
  • UTF-7:它是MIME中使用的Base64编码的变体。
  • 文件名:修改打印符,使用-而不是/,因为文件名不能含/,可以基于修改后的Base64再用于URL
  • URL紧凑:在HTTP环境中使用相当冗长的标识信息时,Base64编码会很有用。
    • 在URL中使用标准Base64 需要将+,和=字符编码为特殊的百分比编码的十六进制序列(+变为%2B/变为%2F=变为%3D),这使得字符串不必要地更长。
    • URL增强中,标准的Base64编码将字符+/分别由-_替代
  • HTML中:atob()btoa(),javaScript方法为网页提供了Base64编码和解码功能。
  • Base64用于编码脚本中的二进制文件(如图像),以避免依赖外部文件。
  • data URI方案可以使用Base64编码来表示文件的内容。例如,背景图像和字体可以在CSS样式表文件中指定为data:URI,而不是在单独的文件中提供。

3. Base58编码(64字符-6=58)

Base58是用于Bitcoin中使用的一种独特的编码方式,主要用于产生Bitcoin的钱包地址。相比Base64,Base58不使用数字0,字母大写O,字母大写I,和字母小写l,以及+/符号。

设计Base58主要的目的是:

  1. 避免混淆。在某些字体下,数字0和字母大写O,以及字母大写I和字母小写l会非常相似。
  2. 不使用"+"和”/“的原因是非字母或数字的字符串作为帐号较难被接受。
  3. 没有标点符号,通常不会被从中间分行。
  4. 大部分的软件支持双击选择整个字符串。

参考