前言
今天來學習UTF8轉Unicode,UTF16轉Unicode以達成UTF8,UTF16之間的互轉。 提煉成函式的公式我並沒有放出來,我的目的只是為了更加理解 字元編碼之間的關係。 如果你需要轉碼方式,可以找其他的庫,或者根據我文章來進行提煉。 基本利用按位操作符 符號運算子就可以完成。
今天這裡只做UTF8轉Unicode,UTF16轉Unicode, 後續轉換可以看前面的文章。
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 ~ 0xdfff
我們已經得知了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
這裡一樣 使用上期例子運算出的結果[0xe4, 0xb8,0x80]進行轉換
由於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('引用錯誤');
}
複製程式碼
這裡編碼轉換就完成了。