webSocket 二進制傳輸基礎準備-UTF-16和UTF-8轉Unicode

前言

今天來學習UTF8轉Unicode,UTF16轉Unicode以達成UTF8,UTF16之間的互轉。
提煉成函數的公式我並無放出來,個人目的只是爲了更加理解 字符編碼之間的關係。
若是你須要轉碼方式,能夠找其餘的庫,或者根據我文章來進行提煉。
基本利用按位操做符 符號運算符就能夠完成。算法

今天這裏只作UTF8轉Unicode,UTF16轉Unicode, 後續轉換能夠看前面的文章。segmentfault

1.基礎準備工做
2.Unicode轉UTF8
3.Unicode轉UTF16數組

UTF16轉Unicode

爲了更好的理解,咱們來使用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 4字節的取值範圍

上面代碼得到了,這個字符的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 等於原值。不懂爲何就回顧上期的表格

UTF8轉Unicode

這裏同樣 使用Unicode轉UTF8那期例子運算出的結果[0xe4, 0xb8,0x80]進行轉換get

因爲JS環境的字符串是UTF16編碼因此我這裏直接使用十六進制串來進行轉換數學

怎麼判斷二進制數據的字符是utf8中的幾字節

根據數據的第一個字節來進行判斷 這個字符是幾個字節。
根據表格找到編碼規則,用來區分這個數據串的字符是幾字節

js是使用小端存儲的,小端存儲是符合咱們的邏輯,大端是邏輯相反
大小端模式

好比 小端存儲是0xxx xxxx 大端存儲就是相反的 xxxx xxx0

utf8編碼規則

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('引用錯誤');
}

這裏編碼轉換就完成了。

相關文章
相關標籤/搜索