今天來學習UTF8轉Unicode,UTF16轉Unicode以達成UTF8,UTF16之間的互轉。
提煉成函數的公式我並無放出來,個人目的只是爲了更加理解 字符編碼之間的關係。
若是你須要轉碼方式,能夠找其餘的庫,或者根據我文章來進行提煉。
基本利用按位操做符 符號運算符就能夠完成。算法
今天這裏只作UTF8轉Unicode,UTF16轉Unicode, 後續轉換能夠看前面的文章。segmentfault
1.基礎準備工做
2.Unicode轉UTF8
3.Unicode轉UTF16數組
爲了更好的理解,咱們來使用Unicode轉UTF-16那一期的結果
來進行UTF16轉Unicode,U+22222轉UTF-16 = [0xd848,0xde22] = '𢈢'(這個字的長度爲二,因此要獲取他全部的charCodeAt)函數
function charCodeAt(str){ var length = str.length, num = 0, utf16Arr = []; for(num; num < length; num++){ utf16Arr[num] = '0x'+str[num].charCodeAt().toString(16); } return utf16Arr; } charCodeAt('𢈢');//['0xD848', '0xDE22']
上面代碼得到了,這個字符的UTF-16編碼數組,JS的字符串所有使用的UTF-16編碼格式
回顧一下UTF-16的編碼方式
將Unicode值減去0x10000,獲得20位長的值,再將其分爲高10位和低10位,分別爲2個字節,高10位和低10位的範圍都在 0 ~ 0x3FF,高10位加0xD800,低十位加0xDC00學習
首先咱們先看字節問題,Unicode值在U+10000 ~ U+10FFFF時,會分爲 兩個2 字節,二進制 8位爲一個字節,因此
UTF-16的四個字節的字符是兩個 16位的二進制
而且根據UTF-16的編碼方式的高位加0xD800 低位加0xDC00得出最小範圍值
高10位最小值爲0xD800,低10爲最小值爲0xDC00
再根據 高10位和低10位的範圍都在 0 ~ 0x3FF得出最大範圍值
高10位最大值爲0xD800+0x3FF,低10爲最大值爲0xDC00+0x3FF編碼
因此高10位的取值範圍爲 0xD800 ~ 0xdbff
低10位的取值範圍爲 高10位的取值範圍爲 0xDC00 ~ 0xdfffcode
咱們已經得知了UTF16編碼的高10位和低10位的取值範圍因此能夠進行判斷 是否須要進行逆推轉換字符串
var strCode = charCodeAt('𢈢'), strCode0 = strCode[0], strCode1 = strCode[1]; if(strCode0 >= 0xD800 && strCode0 <= 0xDBFF){ if(strCode1 !=undefined && strCode1 >= 0xDC00 && strCode1 <= 0xDFFF){ //到了這裏說明這個字符是四個字節就能夠開始進行逆推了 //高10位加0xD800,低十位加0xDC00,因此減去 strCode0 = strCode0 - 0xD800 = 0xd848 - 0xD800 = 0x48 = 72; strCode1 = strCode1 - 0xDC00 = 0xDE22 - 0xDC00 = 0x222 = 546; //高10位和低10位進行拼接。 字符串或者乘法都行 //1 字符串的方式拼接 我用抽象的方式來展示過程 strCode0.toString(2)+strCode1.toString(2) = '1001000' + '1000100010' = '10010001000100010' parseInt(Number('10010001000100010'),2).toString(16)//74274 = 0x12222 //Unicode轉utf16時 將Unicode值減去0x10000,因此再進行加法 0x12222 + 0x10000 = 0x22222; //答案是否是昨天選擇的值呢 //2 利用數學的方式進行轉換 //先給高10位從末位補10個0,也就是乘以10000000000(二進制) = 0x400(16進制) = 1024(十進制) strCode0*0x400 = 0x48*0x400 = 1001000*10000000000 = 1001000 10000000000 = 0x12000 //再加上減去0xDC00後的低10位 0x12000+0x222 = 0x12222 //加上 Unicode轉utf16時 將Unicode值減去的0x10000 0x12222+0x10000 = 0x22222; //Unicode U+22222 = '𢈢'; return; } } //不知足上面條件時,說明UTF16轉Unicode 等於原值。不懂爲何就回顧上期的表格
這裏同樣 使用Unicode轉UTF8那期例子運算出的結果[0xe4, 0xb8,0x80]進行轉換get
因爲JS環境的字符串是UTF16編碼因此我這裏直接使用十六進制串來進行轉換數學
根據數據的第一個字節來進行判斷 這個字符是幾個字節。
根據表格找到編碼規則,用來區分這個數據串的字符是幾字節
js是使用小端存儲的,小端存儲是符合咱們的邏輯,大端是邏輯相反
大小端模式
好比 小端存儲是0xxx xxxx 大端存儲就是相反的 xxxx xxx0
1 字節 0xxx xxxx
2 字節 110x xxxx 10xxxxxx
3 字節 1110 xxxx 10xxxxxx 10xxxxxx
4 字節 1111 0xxx 10xxxxxxx 10xxxxxx 10xxxxxx
js是小端存儲因此只須要按字節位進行對比便可。
utf8各字節編碼規則鮮明差別比較大的是首個字節,因此只須要對比首個字節,就可得知是幾個字節。
根據 按位與的特性,將原碼的x對應,編碼規則的位值轉爲0其餘位保持不變(如有更好的判斷方法,很是期待您的留言)
也可使用 帶符號右移操做符 >>(js並不會轉換正負符號 因此能夠進行放心使用)
對應編碼規則右移n個位來進行判斷值是否爲0,110,1111。(只是猜測之一,並無進行實際驗證,目前僅實踐了下面的方式)
根據按位與用 1 來保留原碼對應的編碼規則位值以及x位值所有轉換爲0 來進行判斷是幾字節
二進制 將x替換爲0 十六進制 1字節 char0 & 1xxx xxxx = 0xxx xxxx char0 & 1000 0000 = 0000 0000 char0 & 0x80 = 0 2字節 char0 & 111x xxxx = 110x xxxx char0 & 1110 0000 = 1100 0000 char0 & 0xE0 = 0xC0 3字節 char0 & 1111 xxxx = 1110 xxxx char0 & 1111 0000 = 1110 0000 char0 & 0xF0 = 0xE0 4字節 char0 & 1111 1xxx = 1111 0xxx char0 & 1111 1000 = 1111 0000 char0 & 0xF8 = 0xF0
上面的判斷規則已經很是明瞭。
下面的轉碼 我就只進行三字節的轉碼規則,其餘 如有興趣,可自行參考3字節的方式進行推算(動手纔是理解最好的方式)
var buffer = new ArrayBuffer(6); var view = new DataView(buffer); view.setUint8(0,0xe4); view.setUint8(1,0xb8); view.setUint8(2,0x80); view.setUint8(3,0xe4); view.setUint8(4,0xb8); view.setUint8(5,0x80); //[[Uint8Array]]: Uint8Array(6) [228, 184, 128, 228, 184, 128] var byteOffset = 0,//起點從1開始 char0, length = view.byteLength;//獲取數據的字節數 while(byteOffset <length){ var char0 = view.getUint8(byteOffset),char1,char2,char3; if((char0 & 0x80) == 0){ //表明是一個字節 byteOffset++; continue; } if((char0 & 0xE0) == 0xC0){ //表明是2個字節 byteOffset+=2; continue; } if((char0 & 0xF0) == 0xE0){ //表明是3個字節 //3 字節編碼規則 1110 xxxx 10xxxxxx 10xxxxxx //進入這個區間時,char0是符合 1110 xxxx的規則的 //利用按位與來進行截取char0對應編碼規則x的位值 也就是 0000 1111 0xF //咱們先轉換第一個字節,二進制 速算法 先將二進制進行轉換16進制(4位二進制爲一位十六進制) //228 & 0xF 1110 0100 & 0000 1111 = 100 = 0x4 = 4 char0 = char0 & 0xF = 4 //第二字節進行轉換 //第二字節編碼規則 10xx xxxx 同理利用按位與 0011 1111 0x3F //184 & 0x3F 1011 1000 & 0011 1111 = 11 1000 = 0x38 = 56 char1 = view.getUint8(byteOffset++); char1 = char1 & 0x3F = 56 //第三字節進行轉換 //第三字節編碼規則 10xx xxxx 同理利用按位與 0011 1111 0x3F //128 & 0x3F 1000 0000 & 0011 1111 = 00 0000 = 0x00 = 0 char2 = view.getUint8(byteOffset++) char2 = char2& 0x3F = 0 //下面纔是重點,咱們已經按字節轉碼完成 那麼如何進行組合呢。 //第一種方法,利用字符串進行拼接。 //'100' + '11 1000' + '00 0000' = '0100 1110 0000 0000' //parseInt(100111000000000,2) = 19968 //String.fromCharCode(19968) = '一' //上面 我抽象的用二進制的過程來展示的,可是實際轉換中 是看不到二進制的。 //parseInt(Number(char0.toString(2) + char1.toString(2) + char2.toString(2)),2) //第二種方式,利用左移操做符 << //編碼規則 1110 xxxx 10xxxxxx 10xxxxxx 第一字節後面有12個x 因此第一字節末位補12個0 //char0 >> 12 = 4 >> 12 //00000000 00000000 00000000 00000100 >> 12 = 0100 0000 0000 0000 = 0x4000 = 16384 //第二本身後面有6個x 因此第二字節補6個0 //char1 >> 6 = 56 >> 12 //00000000 00000000 00000000 00111000 >> 6 = 1110 0000 0000 = 0xE00 = 3584 //第三字節爲最後一個字節因此不須要末位補0 //利用按位或 進行組合 16384 | 3584 | 0 = 0100 1110 0000 0000 = 0x4e00 = 19968 //19968 < 0x10000(U+10000),不須要進行轉碼,調用String.fromCharCode便可 //Unicode碼就轉換完成了。 continue; } if( (char0 & 0xF8) == 0xF0){ //表明是4個字節 byteOffset+=4; continue; } throw RangeError('引用錯誤'); }
這裏編碼轉換就完成了。