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

資訊專欄INFORMATION COLUMN

字符編碼的那些事

kviccn / 1074人閱讀

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

字符編碼的那些事 前言

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

以下內容包括:字符集和字符編碼的關系以及編碼規則,JS的字符編碼,HTML的轉義序列。

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

1bit = 1個二進制位 = 0 或 1

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

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

一、字符集與字符編碼

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

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

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

正因為這個原因,經常會聽到說ASCII編碼、GB2312編碼,甚至Unicode編碼,這種叫法很容易混淆字符集和字符編碼的關系。

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

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

二、ASCII字符集及編碼

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

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

空格"SPACE"碼點:十進制32,十六進制20,二進制00100000

大寫的字母A碼點:十進制65,十六進制41,二進制01000001

Extended ASCII

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

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

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

而對于中文而言,1個字節256個字符顯然不夠,因此中文只能多帶帶制定如GB2312、GBK、GB18030、BIG5字符集了。關于GBXXX編碼可以看這里。

ASCII碼表

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

三、Unicode

Unicode解決了各國自行一套的問題,將世界上所有的符號都納入其中。它符提供了唯一碼點,不論是什么平臺、不論是什么程序、不論是什么語言。

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

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

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

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

這里插播一個名詞:code units 碼元

碼元是各編碼方式的基本單位,長度單位是bit。一個碼點有可能只需要一個碼元,也有可能需要多個碼元。

UTF-x等編碼方式中的數字其實就規定了此編碼方式下的碼元長度。如UTF-8的碼元長度為8bit.......

當一個碼點太大,一碼元長度沒法儲存時,這時就需要其分解成兩個或以上碼元來儲存。

0x10437碼點UTF-16會分解成D801 DC37兩個碼元(每個碼元16bit),UTF-8會分解成f0 90 90 b7四個碼元(每個碼元8bit)

中日韓漢字unicode編碼表

Unicode code converter

1. UTF-8

是在互聯網上使用最廣的一種Unicode的實現方式,

是一種變長的編碼方式。可以使用1~4個字節存儲一個字符,根據不同的符號而變化字節長度。

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

2. UTF-16 2個或4個字節存儲一個字符

2字節:從0x0 - 0xFFFF的碼段(BMP),編碼后的數值和unicode對應的碼點一致

4字節(兩個雙字節):從0x10000 - 0x10FFFF的碼點(SP,已經超過了BMP平面),會根據規則,編碼成一對16bit長的碼元:如0x10437碼點會編碼成D801 DC37,它們叫做代理對(surrogate pair)

4字節代理對的原理

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

大致解說

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

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

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

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

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

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

例如U+10437編碼(?)

0x10437減去0x10000,結果為0x00437,二進制為0000 0000 0100 0011 0111

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

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

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

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

js實現
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所指的是同一的意思。但當引入輔助平面字符后,就稱為UTF-16了。也就是說,UCS-2編碼不能支持在UTF-16中超過2字節的字集。

四、JS字符編碼

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

......

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

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

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

這個問題我一直都有點懵逼,但實際上對于JS的編碼問題應該分成兩個不同的部分看待:

內部:JS引擎是如何解析的?

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

1. 內部:JS引擎解析源碼

引擎會把所有源碼當做是一連串的UTF16碼元,也就是內部是以UTF-16進行編碼的。

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

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

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

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

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

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

但實際上只是語法糖,而這個語法糖很贊,ES6內部對大括號內的碼點進行了UTF-16編碼,不需要自己換算成代理對。

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

"z" === "z"  // true 
"172" === "z" // true 八進制
"x7A" === "z" // true 十六進制

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

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

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

迷之String.prototype.length

其實了解了上面的知識以后,對于字符串的length就不難理解了。

對于JS引擎來說,所有的字符串都是一系列的UTF-16碼元,length指的是碼元的個數(也可以理解為兩個字節等于1個length),而不是字符個數。當某個字符是4個字節的UTF-16編碼時,這時一個字符的length就為2。但是中文的length卻始終為1,這是因為中文的碼點范圍U+4E00 - U+9FA5,都在BMP平面內,UTF-16編碼只需要2個字節。

來看例子~

// 還是拿這個字
"?" === "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也不要灰心,只要識別兩個相鄰的碼元是否形成代理對的關系(原理在上面有講~),是的話把它們視為一個整體。

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

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

我們可以以不同的編碼方式來保存源碼,但如果瀏覽器解碼方案和源碼保存時的編碼方案不同,就會導致亂碼。

當瀏覽器在加載一個

閱讀需要支付1元查看
<