国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

字符編碼的那些事

shadajin / 2975人閱讀

摘要:字符編碼的那些事前言之前看到中對(duì)擴(kuò)展了不少新特性,字符串操作更加友好,比如,,。其中涉及到不少字符編碼的知識(shí),為了更好理解這些新特性,本文對(duì)字符編碼相關(guān)知識(shí)做一個(gè)較全面的梳理和總結(jié)。

字符編碼的那些事 前言

之前看到ES6中對(duì)String擴(kuò)展了不少新特性,字符串操作更加友好,比如"u{1f914}",codePointAt(),String.fromCodePoint()。其中涉及到不少字符編碼的知識(shí),為了更好理解這些新特性,本文對(duì)字符編碼相關(guān)知識(shí)做一個(gè)較全面的梳理和總結(jié)。

以下內(nèi)容包括:字符集和字符編碼的關(guān)系以及編碼規(guī)則,JS的字符編碼,HTML的轉(zhuǎn)義序列。

首先,在前言里面回顧一下位與字節(jié)(小b和大B)的最最基礎(chǔ)知識(shí)。

1bit = 1個(gè)二進(jìn)制位 = 0 或 1

8bit = 8個(gè)0或1(2^8=256個(gè)組合)= 1字節(jié)Byte

值得一提,在計(jì)算帶寬大小(bps)的時(shí)候要注意是以bit作為單位。

一、字符集與字符編碼

Unicode、ASCII、GB2312、GBK、BIG5都屬于是字符集(character set),每個(gè)字符集包含的字符種類和數(shù)量都不一樣,每個(gè)字符有各自的編號(hào)作為唯一標(biāo)識(shí)。

那么它們是通過什么方式進(jìn)行編號(hào)(以下都稱為碼點(diǎn))的呢?不同的字符集有不同的方案,對(duì)于ASCII、GB2312、GBK、BIG5來說,實(shí)行“壟斷”政策,即只允許使用它規(guī)定的編碼方案,也可以認(rèn)為它即是字符集也是字符編碼。而Unicode實(shí)行“百家爭(zhēng)鳴”政策,提供了UTF-8/UTF-16/UTF-32幾種備選的字符編碼方案,所以這時(shí)Unicode僅僅是字符集,UTF-X才是字符編碼

各個(gè)字符集的具體編碼方案可以看這里

正因?yàn)檫@個(gè)原因,經(jīng)常會(huì)聽到說ASCII編碼、GB2312編碼,甚至Unicode編碼,這種叫法很容易混淆字符集和字符編碼的關(guān)系。

弄清楚字符集與字符編碼的關(guān)系之后,我們可以知道如果某個(gè)字符想從UTF-8編碼轉(zhuǎn)成GBK編碼的話,那就必須先將其unicode碼點(diǎn)換算成GBK碼點(diǎn),再進(jìn)行GBK編碼。

下面我們主要看看ASCII和Unicode這兩種字符集(編碼)。

二、ASCII字符集及編碼

ASCII是最古老原始的字符集和編碼,主要是滿足英語字符的需要,畢竟計(jì)算機(jī)是從人家老美那誕生的。

每個(gè)字符用一個(gè)字節(jié)(8bit)來儲(chǔ)存,一共定義了128個(gè)字符,前32個(gè)字符是非打印控制字符(回車換行等)。雖然一個(gè)字節(jié)最多可以定義256種字符,但是ASCII只用了1個(gè)字節(jié)的后面7位,最前面統(tǒng)一都為0。

空格"SPACE"碼點(diǎn):十進(jìn)制32,十六進(jìn)制20,二進(jìn)制00100000

大寫的字母A碼點(diǎn):十進(jìn)制65,十六進(jìn)制41,二進(jìn)制01000001

Extended ASCII

ASCII只有128個(gè)字符,其他語言不夠用了,怎么辦?

別忘了ASCII只用了后面7位,利用空閑的最高位,這樣可以擴(kuò)容到256個(gè)字符,成為擴(kuò)展ASCII碼(EASCII)。所以0-127碼點(diǎn)表示的字符是一樣的,不一樣的只是128-255碼段。

由于只能多擴(kuò)容128個(gè)字符,而各國(guó)語言中的字符又各不相同,為了滿足不同地區(qū)的更多字符的需求,所以擴(kuò)容字符的含義不可能都一樣。這里就會(huì)出現(xiàn)如ASCII碼表“阿拉伯字符(ASMO-708)碼”擴(kuò)展ASCII,“泰語(Windows)碼”擴(kuò)展ASCII。

而對(duì)于中文而言,1個(gè)字節(jié)256個(gè)字符顯然不夠,因此中文只能多帶帶制定如GB2312、GBK、GB18030、BIG5字符集了。關(guān)于GBXXX編碼可以看這里。

ASCII碼表

看到這里,有種貴圈真亂的感覺,各國(guó)都自行一套字符集及編碼,這就不利于溝通交流阿。直到Unicode出現(xiàn)。

三、Unicode

Unicode解決了各國(guó)自行一套的問題,將世界上所有的符號(hào)都納入其中。它符提供了唯一碼點(diǎn),不論是什么平臺(tái)、不論是什么程序、不論是什么語言。

碼點(diǎn)code point范圍從 0x0 - 0x10FFFF,共分為17個(gè)Plane,每個(gè)Plane中有65536個(gè)字符,共可容納: 17*(16*16*16*16)= 1114112 個(gè)字符。

第一個(gè)平面稱為基本多語言平面(Basic Multilingual Plane, BMP)。其他平面稱為輔助平面(Supplementary Planes, SP),或astral Plane。

BMP內(nèi),從U+D800到U+DFFF之間的碼位區(qū)塊是永久保留不映射到Unicode字符。后面介紹的UTF-16就利用保留下來的0xD800-0xDFFF區(qū)段的碼位來對(duì)輔助平面的字符的碼位進(jìn)行編碼。

前面說到Unicode只是字符集,具體碼點(diǎn)怎么儲(chǔ)存,可選擇UTF-8、UTF-16或者UTF-32編碼方式。

這里插播一個(gè)名詞:code units 碼元

碼元是各編碼方式的基本單位,長(zhǎng)度單位是bit。一個(gè)碼點(diǎn)有可能只需要一個(gè)碼元,也有可能需要多個(gè)碼元。

UTF-x等編碼方式中的數(shù)字其實(shí)就規(guī)定了此編碼方式下的碼元長(zhǎng)度。如UTF-8的碼元長(zhǎng)度為8bit.......

當(dāng)一個(gè)碼點(diǎn)太大,一碼元長(zhǎng)度沒法儲(chǔ)存時(shí),這時(shí)就需要其分解成兩個(gè)或以上碼元來儲(chǔ)存。

0x10437碼點(diǎn)UTF-16會(huì)分解成D801 DC37兩個(gè)碼元(每個(gè)碼元16bit),UTF-8會(huì)分解成f0 90 90 b7四個(gè)碼元(每個(gè)碼元8bit)

中日韓漢字unicode編碼表

Unicode code converter

1. UTF-8

是在互聯(lián)網(wǎng)上使用最廣的一種Unicode的實(shí)現(xiàn)方式,

是一種變長(zhǎng)的編碼方式。可以使用1~4個(gè)字節(jié)存儲(chǔ)一個(gè)字符,根據(jù)不同的符號(hào)而變化字節(jié)長(zhǎng)度。

UTF-8的編碼規(guī)則
Unicode碼點(diǎn)范圍 UTF-8編碼方式(二進(jìn)制) UTF-8編碼所需大小 規(guī)則 備注
U+0000 0000 - U+0000 007F 0xxxxxxx 1byte 字節(jié)的第一位設(shè)為0;后面7位為這個(gè)符號(hào)的unicode碼。 英語字母,UTF-8編碼和ASCII碼是相同的
U+0000 0080 - U+0000 07FF 110xxxxx 10xxxxxx 2byte 第一個(gè)字節(jié)前兩位是1,第三位是0;后面字節(jié)的前兩位一律設(shè)為10
U+0000 0800 - U+0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 3byte 第一個(gè)字節(jié)前三位是1,第四位是0;后面字節(jié)的前兩位一律設(shè)為10 漢字(U+4E00 - U+9FA5)在UTF-8里的編碼都是 3 個(gè)字節(jié)
U+0001 0000 - U+0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 4byte 第一個(gè)字節(jié)前四位是1,第五位是0;后面字節(jié)的前兩位一律設(shè)為10

2. UTF-16 2個(gè)或4個(gè)字節(jié)存儲(chǔ)一個(gè)字符

2字節(jié):從0x0 - 0xFFFF的碼段(BMP),編碼后的數(shù)值和unicode對(duì)應(yīng)的碼點(diǎn)一致

4字節(jié)(兩個(gè)雙字節(jié)):從0x10000 - 0x10FFFF的碼點(diǎn)(SP,已經(jīng)超過了BMP平面),會(huì)根據(jù)規(guī)則,編碼成一對(duì)16bit長(zhǎng)的碼元:如0x10437碼點(diǎn)會(huì)編碼成D801 DC37,它們叫做代理對(duì)(surrogate pair)

4字節(jié)代理對(duì)的原理

從上面D801 DC37的例子可以發(fā)現(xiàn),這兩個(gè)碼點(diǎn)都落在了BMP平面的碼點(diǎn)范圍之內(nèi),并且都屬于U+D800到U+DFFF碼段。沒錯(cuò),就是通過一系列規(guī)則,把超出BMP平面的碼點(diǎn)(U+10437)轉(zhuǎn)換成兩個(gè)屬于BMP平面的碼點(diǎn)——U+D800到U+DFFF碼段之間(U+D801和U+DC37)。

大致解說

SP平面中的碼點(diǎn)范圍是從U+10000到U+10FFFF,共計(jì)FFFFF個(gè),即2^20=1,048,576個(gè),需要20位來表示。

如果用兩個(gè)雙字節(jié)長(zhǎng)的碼點(diǎn)組成的序列來表示,第一個(gè)碼點(diǎn)(稱為高位代理)要容納上述20位的前10位,第二個(gè)碼點(diǎn)(稱為低位代理)容納上述20位的后10位。前后分別需要2^10=1024個(gè)碼點(diǎn)來代理。而BMP平面的U+D800到U+DFFF碼段正好有2048個(gè)碼點(diǎn),足以滿足高位代理與低位代理的需要。

因此需要將U+D800 - U+DFFF分為兩段

一段為高位代理初始值U+D800:U+D800 到 U+DBFF 之間的保留碼點(diǎn)用于高位代理(leading surrogates)

一段為低位代理初始值U+DC00:U+DC00 到 U+DFFF 之間的保留碼點(diǎn)則用于低位代理(trailing surrogates)

兩段之間間隔2^10=1024,剛好各自能夠滿足前后10位

例如U+10437編碼(?)

0x10437減去0x10000,結(jié)果為0x00437,二進(jìn)制為0000 0000 0100 0011 0111

分區(qū)它的上10位值和下10位值(使用二進(jìn)制):0000000001 and 0000110111

添加0xD800到上值,以形成高位:0xD800 + 0x0001 = 0xD801。

添加0xDC00到下值,以形成低位:0xDC00 + 0x0037 = 0xDC37。

得出它的UTF-16編碼為D801 DC37

js實(shí)現(xiàn)
function findSurrogatesPair(codePoint) {
    var offset = codePoint - 0x10000;
    var lead = 0xd800 + (offset >> 10);
    var tail = 0xdc00 + (offset & 0x3ff); // 和1023 位與 把前十位都置為0
  
    return [lead.toString(16) + tail.toString(16)];
}

// example
var str = "?";
var cp = str.codePointAt(0);
utf16Encode(cp); // return ["d83e", "dd14"]
console.log("ud83eudd14"); // ?
UTF-16是UCS-2的父集

在沒有輔助平面字符前,UTF-16與UCS-2所指的是同一的意思。但當(dāng)引入輔助平面字符后,就稱為UTF-16了。也就是說,UCS-2編碼不能支持在UTF-16中超過2字節(jié)的字集。

四、JS字符編碼

阮老師的ES6教程字符串的擴(kuò)展里面的第一小節(jié)字符的unicode表示法中提到:

......

有了這種表示法之后,JavaScript 共有6種方法可以表示一個(gè)字符。

"z" === "z"  // true
"172" === "z" // true
"x7A" === "z" // true
"u007A" === "z" // true
"u{7A}" === "z" // true

這里面的u007A看起來JS好像是UTF-16編碼,但是平時(shí)加載一個(gè)JS時(shí)指定的charset又好像是UTF-8或者GBK?那JS到底是以什么來編碼的?

這個(gè)問題我一直都有點(diǎn)懵逼,但實(shí)際上對(duì)于JS的編碼問題應(yīng)該分成兩個(gè)不同的部分看待:

內(nèi)部:JS引擎是如何解析的?

外部:瀏覽器是以什么編碼來解析JS腳本的?

1. 內(nèi)部:JS引擎解析源碼

引擎會(huì)把所有源碼當(dāng)做是一連串的UTF16碼元,也就是內(nèi)部是以UTF-16進(jìn)行編碼的。

var fu006Fu006F = 123;
console.log(foo); // 123
console.log(u0066u006Fu006F); // 123
var foo = "12u0033"; // 123

// 中文
var 騰 = "123";
console.log(u817e); // 123

// 4字節(jié)字符
var bar = "?";
console.log("uD842uDFB7"); // "?"

上面的例子可以看到,無論是字符串還是變量,無論是BMP還是SP上的字符,都可以使用UTF-16碼元來表示。

那ES6中的大括號(hào)表示法呢?看起來并不需要UTF-16編碼,直接用大括號(hào)包裹碼點(diǎn)就好了。

"u{20BB7}" === "uD842uDFB7" // 竟然全等

但實(shí)際上只是語法糖,而這個(gè)語法糖很贊,ES6內(nèi)部對(duì)大括號(hào)內(nèi)的碼點(diǎn)進(jìn)行了UTF-16編碼,不需要自己換算成代理對(duì)。

此外,上面還有三種表示法看起來怪怪的

"z" === "z"  // true 
"172" === "z" // true 八進(jìn)制
"x7A" === "z" // true 十六進(jìn)制

z實(shí)際上是用于轉(zhuǎn)義特殊字符,如r n t " "等,而z這種非特殊含義字符則等于它本身

八進(jìn)制表示法,反斜杠后的取值范圍是0-377(十進(jìn)制的0-255),官方說法是用來表示Latin-1編碼字符

十六進(jìn)制表示法,取值范圍是00-FF,和上面的八進(jìn)制表示目的是一樣的

迷之String.prototype.length

其實(shí)了解了上面的知識(shí)以后,對(duì)于字符串的length就不難理解了。

對(duì)于JS引擎來說,所有的字符串都是一系列的UTF-16碼元,length指的是碼元的個(gè)數(shù)(也可以理解為兩個(gè)字節(jié)等于1個(gè)length),而不是字符個(gè)數(shù)。當(dāng)某個(gè)字符是4個(gè)字節(jié)的UTF-16編碼時(shí),這時(shí)一個(gè)字符的length就為2。但是中文的length卻始終為1,這是因?yàn)橹形牡拇a點(diǎn)范圍U+4E00 - U+9FA5,都在BMP平面內(nèi),UTF-16編碼只需要2個(gè)字節(jié)。

來看例子~

// 還是拿這個(gè)字
"?" === "uD842uDFB7";
"uD842uDFB7" === "u{20BB7}";
var foo = "?";
var bar = "uD842uDFB7";
var baz = "u{20BB7}";
console.log(foo.length, bar.length, baz.length); // 2 2 2

而我們需要獲取“正確”的length值該怎么辦?

ES6輕松解決:Array.from(str).length

不是ES6也不要灰心,只要識(shí)別兩個(gè)相鄰的碼元是否形成代理對(duì)的關(guān)系(原理在上面有講~),是的話把它們視為一個(gè)整體。

function getRealLen(str) {
    var reg = /[ud800-udbff][udc00-udfff]/g; // /[高位代理][低位代理]/g
    return str.replace(reg, "i").length;
}

getRealLen("?"); // 1
getRealLen("?????"); // 5
2. 外部:瀏覽器解析JS腳本

我們可以以不同的編碼方式來保存源碼,但如果瀏覽器解碼方案和源碼保存時(shí)的編碼方案不同,就會(huì)導(dǎo)致亂碼。

當(dāng)瀏覽器在加載一個(gè)

閱讀需要支付1元查看
<