Encode - 字符编码那些事

AI 摘要: 本文主要介绍了编码的发展过程,从ASCII到Unicode再到UTF-8和UTF-16的出现,以及Base64和Base58编码的应用。ASCII是早期的编码方式,GB2312和GBK是各国自主编码阶段的结果,Unicode解决了全球编码的问题,UTF-8和UTF-16则是基于Unicode的存储和传输效率考虑的编码方式。Base64是一种用于表示二进制数据的编码方式,而Base58是比特币中使用的一种独特的编码方式。

编码发展

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 中处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 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替换为+

转码效果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//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 头内部。
1
2
3
4
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. 大部分的软件支持双击选择整个字符串。

参考