字符编码:ASCII、unicode、UTF-8、GB*

数字计算机中的存储器唯一可以存储的是比特(bit),因此如果要想在计算机上处理信息,就必须把它们按位存储。为了将文本表示为数字形式,我们需要构建一种系统来为每一个字母赋予一个唯一的编码。数字和标点符号也算做文本的一种形式,所以它们也必须拥有自己的编码。

所有由符号所表示的字母和数字(Alphanumeric)都需要编码。具有这种功能的系统被称为字符编码集(Coded Character Set),系统内的每个独立编码称为字符编码(Character Codes)。

所有人都遵循并使用统一化的字符编码,不同计算机之间就可以互相交流文本信息。

  • 字节(Byte): 是计量存储容量的单位,是构成信息的一个很小的单位,上面还有 KB、MB、GB、TB、PB、EB、ZB、YB 等;下面还有 bit(位)。
  • 字符: 各种文字、符号的总称。例如文字、标点、图形、数字、字母等等。
  • 字符集: 顾名思义,就是一定数量的字符组成的集合,字符集种类比较多,而且每个字符集包含的字符个数也不同,常见字符集主要有:ASCII 字符集、GB2312 字符集、BIG5 字符集、 GB18030 字符集、Unicode 字符集等,字符集为每一个【字符】分配一个唯一的 ID(学名为码位 / 码点 / Code Point)。
  • 字符编码: 将字符集中的每个字符映射为字节流的实现方案(编码方案),即属于将【码位】转换为字节序列的规则,便于计算机存储和传输;常见的字符编码有 ASCII 编码、UTF-8 编码、GBK 编码、Base64 编码等。某种意义上来说,字符集与字符编码有种对应关系,例如 ASCII 字符集对应有 ASCII 编码。
  • 编码与解码: 编码的过程是将字符转换成字节流,解码的过程是将字节流解析为字符。

ASCII

ASCII (American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言,共定义了128个字符 。从1967年正式公布至今,它一直是计算机产业中最重要的标准。

ASCII 码一共规定了128个字符的编码,比如空格SPACE是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0。

ASCII 字符集沿用至今,但它最大的缺点在于只适用于以英语为主的国家,在面对数以万计的中国、日本、韩国的象形文字,以及奇怪的朝鲜文音节等各国语言面前就不够用了。

为了应对各种语言的编码需求,近几十年来出现了许多不同版本的扩展的 ASCII 码,多个不同的版本严重影响了编码的一致性,导致了混淆和不兼容。其中通用的 ASCII 码字符,是用单个字节编码表示的,相比而言,成千上万的象形文字则是双字节编码,这在无形之中增加了使用这种字符集的难度。

Unicode

Unicode 是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。

Unicode,统一码,也叫万国码,学名是"Universal Multiple-Octet Coded Character Set",简称为 UCS。现在用的是 UCS-2,即2个字节编码,而 UCS-4 是为了防止将来2个字节不够用才开发的。全世界所有的人类语言,尤其是经常出现在计算机通信过程中的语言,都可以使用同一个编码系统,而且这种系统还具备很高的扩展性。

Unicode 至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2021年9月公布的 14.0.0,已经收录超过 14 万个字符(第十万个字符在2005年获采纳)。Unicode 除了视觉上的字形、编码方法、标准的字符编码资料外,还包含了字符特性(如大小写字母)、书写方向、拆分标准等特性的资料库。

需要注意的是, Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

比如,汉字严的 Unicode 是十六进制数 4E25,转换成二进制数足足有15 位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:

  • 出现了 Unicode 的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示 Unicode。
  • Unicode 在很长一段时间内无法推广,直到互联网的出现。

UTF-8

虽然 Unicode 字符集中的码位唯一,但由于计算机存储数据通常是以字节为单位的,而且出于兼容之前的 ASCII、大数小段数段、节省存储空间等诸多原因,通常情况下,我们需要一种具体的编码方式来对字符码位进行存储。

比较常见的基于 Unicode 字符集的编码方式有 UTF-8、UTF-16 及 UTF-32。

UTF-8(8位元,Universal Character Set/Unicode Transformation Format)是针对 Unicode 的一种可变长度字符编码。它可以用来表示 Unicode 标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部分修改后,便可继续使用。

UTF-8 使用 1~4 字节为每个字符编码:

  • 128个 US-ASCII 字符只需一个字节编码(Unicode 范围由 U+0000至 U+007F)。对于只有一个字节的符号,字节的第一位设为0,后面 7 位为这个符号的 Unicode 码。

  • 对于 n 字节的符号,第一个字节的前 n 位都设为 1,第 n + 1 位设为0,后面字节的前两位一律设为 10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

    • 2字节编码:带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母(Unicode范围由U+0080~U+07FF)。
    • 3字节编码:其他语言的字符(包括中日韩文字、东南亚文字、中东文字等)包含了大部分常用字。
    • 4字节编码:其他极少使用的语言字符。

UTF-8 因可以兼容 ASCII,且较为节约存储空间,而被广泛使用。

UTF-16 使用 2 或 4 个字节为每个字符编码,但 UTF-16 却无法兼容于 ASCII 编码。

UTF-32 使用四个字节为每个字符编码,使得 UTF-32 占用空间通常会是其它编码的二到四倍。

现行桌面系统中,Windows 内部采用了 UTF-16 的编码方式,而 Mac OS、Linux 等则采用了 UTF-8 编码方式。

GB2312

除了基于 Unicode 字符集的 UTF-8、UTF-16 等编码外,在中文语言地区,我们还有一些常见的字符集及其编码方式,GB2312、Big5 就是其中影响最大、使用最广泛的两种。

GB2312 或 GB2312-80 是中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,又称 GB 0,由中国国家标准总局发布,1981 年 5 月 1 日实施。GB2312 编码通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持 GB 2312。

GB2312 的出现先于 Unicode。GB2312 标准共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符,而在编码上,是采用了基于区位码的一种编码方式,采用 2 字节表示一个中文字符。

  • GB 2312 的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆 99.75% 的使用频率。
  • 对于人名、古汉语等方面出现的罕用字,GB 2312 不能处理,这导致了后来 GBK 及 GB 18030 汉字字符集的出现。

不同的编码方式对于相同的二进制字符串的解释是不同的。常见的,如果一个 UTF-8 编码的网页中的字符串按照 GB2312 编码进行显示,就会出现乱码。

Big5

BIG5 则常见于繁体中文,俗称“大五码”。BIG5 是长期以来的繁体中文的业界标准,共收录了 13060 个中文字,也采用了 2 字节的方式来表示繁体中文。BIG5 在中国台湾、香港、澳门等地区有着广泛的使用。

而 BIG5 和 GB2312 之间的乱码则在中文地区软件中有着“悠久”的历史。不过随着 Unicode 的使用和发展,以及软件系统对多种编码的支持,程序发生乱码的现象也越来越少。

GBK

GBK汉字内码扩展规范K 为汉语拼音 Kuo Zhan(扩展)中“扩”字的声母。英文全称 Chinese Internal Code Specification。

GBK 共收入 21886 个汉字和图形符号,包括:

  • GB 2312 中的全部汉字、非汉字符号。
  • BIG5 中的全部汉字。
  • 与 ISO 10646 相应的国家标准 GB 13000 中的其它 CJK 汉字,以上合计 20902 个汉字。
  • 其它汉字、部首、符号,共计 984 个。

GBK 向下与 GB 2312 完全兼容,向上支持 ISO 10646 国际标准,在前者向后者过渡过程中起到的承上启下的作用。

GB18030

GB18030,全称:国家标准 GB 18030-2005《信息技术中文编码字符集》,是中华人民共和国现时最新的内码字集,是 GB 18030-2000《信息技术信息交换用汉字编码字符集基本集的扩充》的修订版。GB 18030 与 GB 2312-1980 和 GBK 兼容,共收录汉字70244个。

  • 与 UTF-8 相同,采用多字节编码,每个字可以由 1 个、2 个或 4 个字节组成。
  • 编码空间庞大,最多可定义 161 万个字符。
  • 支持中国国内少数民族的文字,不需要动用造字区。
  • 汉字收录范围包含繁体汉字以及日韩汉字

GB 18030 编码是一二四字节变长编码。

  • 单字节,其值从 0 到 0x7F,与 ASCII 编码兼容。
  • 双字节,第一个字节的值从 0x81 到 0xFE,第二个字节的值从 0x40 到 0xFE(不包括0x7F),与 GBK 标准兼容。
  • 四字节,第一个字节的值从 0x81 到 0xFE,第二个字节的值从 0x30 到 0x39,第三个字节从0x81 到 0xFE,第四个字节从 0x30 到 0x39。

编码解码方法

我们在操作 URL 的时候经常需要处理其中的特殊字符,因为在 URL 中有很多字符是有特殊意义的,比如 ?、&、= 等等,如果在处理 URL 的时候不对这些特殊字符进行转义,那么浏览器很可能会错误解析 URL,所以一般的编程语言都提供了对 URL 进行编码解码的 API。

URI 和 URL 的区别

URI 是 uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。

URL 是 uniform resource locator,统一资源定位器,它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何定位这个资源。

笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。

URI 一般由三部分组成:

  • 访问资源的命名机制;

  • 存放资源的主机名;

  • 资源自身的名称,由路径表示。

https://www.example.com/path/source#hash
file://user/lizh/demo-lizh/test.txt
www.example.com/path/source#hash

URL 的格式一般由下列三部分组成:

  • 第一部分是协议(或称为服务方式);
  • 第二部分是存有该资源的主机IP地址(有时也包括端口号);
  • 第三部分是主机资源的具体地址。
https://www.example.com/path/source#hash
file://user/lizh/demo-lizh/test.txt
# 注意:www.example.com/path/source#hash 不是有效的URL

你可能觉得 URI 和 URL 可能是相同的概念,其实并不是,URI 和 URL 都定义了资源是什么,但 URL 还定义了该如何访问资源。URL 是一种具体的 URI,它是 URI 的一个子集,它不仅唯一标识资源,而且还提供了定位该资源的信息。URI 是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而 URL 则必须提供足够的信息来定位,是绝对的。

URL 编码

URL 编码(URL encoding),也称百分号编码,是特定上下文的统一资源定位符 (URL)的编码机制,实际上也适用于统一资源标志符(URI)的编码。URL 的编码格式采用的是 ASCII 码,而不是 Unicode,这也就是说你不能在 URL 中包含任何非 ASCII 字符。否则,如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,可能会造成问题。

RFC3986 文档规定,URL 中只允许包含未保留字符以及所有保留字符,其它字符必须用 URL(百分号)编码

  • 保留字符是那些具有特殊含义的字符(18个):: / ? # [ ] @ ! $ & ' ( ) * + , ; =
  • 未保留字符没有这些特殊含义的字符(66个):a-z A-Z 0-9 - _ . ~
  • 受限字符或不安全字符,直接放在 Url 中的时候,可能会引起解析程序的歧义。比如:空格、% < > " { }| \ ^ ~ [ ] '0x00-0x1F0x7F>0x7F

注意:保留字符和未保留字符也有可能是受限或不安全字符。

比如: URL 参数字符串中使用 key=value 键值对这样的形式来传参,键值对之间以 & 符号分隔,如 /index?v=1.0.0&author=lizhao。如果你的 value 字符串中包含了 = 或者 &,那么势必会造成接收 URL 的服务器解析错误。

escape()/unescape()

escape() 方法通过用 十六进制转义序列 替换某些字符来对 URI 进行编码。

escape 编码规则:

  • 不对 ASCII 字母、数字进行编码,即 a-z A-Z 0-9。(62个)
  • 不对 * @ - _ + . / 进行编码。(7个)
  • 其他所有的字符都会被转义序列替换为 %xx 或 %uxxxx(x表示十六进制的数字)的转义序列。从 \u000 到 \u00ff 的 Unicode 字符由转义序列 %xx 替代,其他所有 Unicode 字符由 %uxxxx 序列替代。

escape的不编码集(69个):

A-Z a-z 0-9 * @ - _ + . /

注意: escape()/unescape() 已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。

使用 encodeURI 或 encodeURIComponent 代替。

escape("abc123");     // "abc123"
escape("äöü");        // "%E4%F6%FC"
escape("ć");          // "%u0107"

escape("@*_+-./");    // "@*_+-./"

unescape() 方法是对 escape() 方法编码的字符串进行解码。

encodeURI()/decodeURI()

encodeURI() 方法通过用 1~4个字节表示字符的 UTF-8 编码的转义序列 替换某些字符来对 URI 进行编码。

以下示例显示了 URI 可能包含的所有部分,请注意某些字符表示特殊含义:

http://username:password@www.example.com:80/path/to/file.php?foo=316&bar=this+has+spaces#anchor
# 包含的保留字符9个:/?:@&=+.#

encodeURI 的编码规则:

  • 不对 ASCII 字母、数字进行编码,即 a-z A-Z 0-9。(62个)
  • 不对 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) # 进行编码。(20个)
  • 其他所有的字符都会被转义序列替换。

encodeURI 的不编码集(82个),即 82 = 保留字符(18个)+ 未保留字符(66个) - 不安全的保留字符[](2个):

A-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #

decodeURI() 方法对 encodeURI() 方法编码的字符串进行解码。

注意 escape() 和 encodeURI() 的区别:

  • 两者的转换结果不一样:escape() 方法转换为 十六进制转义序列 ,而 encodeURI() 方法转换为 UTF-8 编码的转义序列

    escape("兆")    // %u5146
    encodeURI("兆") // %E5%85%86
    
  • escape 的不编码集(69个) 是 encodeURI 的不编码集(82个) 的子集。即 encodeURI() 方法能编码的,escape() 方法一定能编码,反之则不然。

encodeURICompoent()/decodeURICompoent()

encodeURIComponent() 方法通过用 1~4个字节表示字符的 UTF-8 编码的转义序列 替换某些字符来对 URI 进行编码。

encodeURIComponent 的编码规则:

  • 不对 ASCII 字母、数字进行编码,即 a-z A-Z 0-9。(62个)
  • 不对 - _ . ! ~ * ' ( ) 进行编码。(9个)
  • 其他所有的字符都会被转义序列替换。

encodeURIComponent 的不编码集(71个):

A-Z a-z 0-9 - _ . ! ~ * ' ( )

encodeURIComponent () 方法对 decodeURICompoent() 方法编码的字符串进行解码。

注意 encodeURI() 和 encodeURIComponent () 的区别:

它们的主要区别在于,encodeURICompoent() 方法假定参数是 URI 的一部分,例如,协议、主机名、路径或查询字符串。因此,它将转义用于分隔 URI 各个部分的标点符号。而 encodeURI() 方法仅把它们视为普通的 ASCII 字符,并没有转换。

换句话说,encodeURICompoent() 方法会转换 URI 中的分隔符号,而 encodeURI() 方法不会。

再简单点说,encodeURICompoent() 方法会多转换以下符号(11个):

; , / ? : @ & = + $ #
encodeURI(";,/?:@&=+$#")          // ;,/?:@&=+$#
encodeURIComponent(";,/?:@&=+$#") // %3B%2C%2F%3F%3A%40%26%3D%2B%24%23

请注意,encodeURI() 它本身不能形成正确的 HTTP GET 和 POST 请求,例如:XMLHttpRequest,因为 & + = 没有被编码,它们在 GET 和 POST 请求中被视为特殊字符 。而 encodeURIComponent() 可以对这些字符进行了编码。

为了避免服务器收到不可预知的请求,对任何用户输入的作为 URI 部分的内容你都需要用 encodeURIComponent 进行转义。比如,一个用户可能会输入 "Thyme &time=again" 作为 URI 变量 key 的一部分。如果不使用 encodeURIComponent 对此内容进行转义,服务器得到的将是 key=Thyme%20&time=again。请注意,"&" 符号和 "=" 符号产生了一个新的键值对,所以服务器得到两个键值对(一个键值对是 key=Thyme,另一个则是time=again),而不是一个键值对。

Unicode转换

JavaScript 中,unicode 是以十六进制代码外加开头 \u 表示的字符串,即 \uxxxx。

// 转unicode
"兆".charCodeAt(0) // 20806
"兆".charCodeAt(0).toString(16) // 5146,对应unicode \u5146

console.log('\u5146') // 兆
'\u5146' === '兆' // true
String.fromCharCode(20806) // 兆
function encodeUnicode(str) {
    let uString = ''
    for (let i = 0; i < str.length; i ++) {
        let temp = str.charCodeAt(i).toString(16).toUpperCase()
        while (temp.length < 4) {
            temp = '0' + temp
        }
        temp = '\\u' + temp
        uString += temp
    }
    return uString
}

参考资料

URI和URL的区别

URL编码的奥秘

MDN-encodeURI

MDN-encodeURIComponent

© lizhao all right reserved,powered by Gitbook文件修订时间: 2021-12-01 01:11:18

results matching ""

    No results matching ""