本文屬於 字符編碼系列文章之一,更多請前往 字符編碼系列。javascript
不一樣編碼轉換的理論基礎html
不一樣的編碼直接如何轉換的,這裏先簡單的描述下UTF-1六、UTF-八、GBK直接的轉換過程。java
因爲本文是基於JavaScript的,而JS如今的編碼能夠認爲是UTF-16,因此都會通過UTF-16中轉。git
這二者都是Unicode,因此有一個大前提就是碼點一致,僅僅是對於碼點的編碼方式不一致而已,由於UTF-16能夠認爲是固定2字節的實現(4字節的比較少見),因此參考以下Unicode和UTF-8轉換關係表便可:github
Unicode編碼 | UTF-8字節流 |
---|---|
U+00000000 - U+0000007F | 0xxxxxxx |
U+00000080 - U+000007FF | 110xxxxx 10xxxxxx |
U+00000800 - U+0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
U+00010000 - U+001FFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
U+00200000 - U+03FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
U+04000000 - U+7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
因此UTF16和UTF8之間的相互轉換能夠經過上表的轉換表來實現,判斷Unicode碼所在的區間就能夠獲得這個字符是由幾個字節所組成,以後經過移位來實現,分爲新的多個字節來存儲。編程
UTF-16和GBK直接的轉換就稍微複雜點,由於Unicode和GBK的碼點不一致,所以須要GBK個Unicode的碼點映射關係表才能進行相應轉換。segmentfault
這裏GBK和Unicode的碼點映射表因爲太長了,就不單獨列出來,能夠參考:Unicode編碼和GBK的轉換映射表 (若是連接不可用能夠自動搜索或者參考博客中的源碼連接)數組
而後通用,拿到Unicode碼點後能夠根據映射錶轉換爲GBK碼點,而後用GBK的編碼方式編碼便可完成轉換編程語言
Step2:判斷該Unicode碼所在的範圍,根據不一樣的範圍,來決定存儲它的字節長度。編碼
/** * @description 將utf-16編碼字符串轉爲utf-8編碼字符串 * @param {String} str 傳入的 utf16編碼字符串(javascript內置的就是utf16編碼) * @return {String} utf8編碼的字符串,js打印會有亂碼 */ exports.utf16StrToUtf8Str = function(str) { if (!str) { // ''字符屬於ascii碼,因此沒必要擔憂不一樣編碼的轉換問題 return ''; } // res是用來存放結果的字符數組,最終會轉爲字符串返回 var res = [], len = str.length; for (var i = 0; i < len; i++) { var code = str.charCodeAt(i); if (code > 0x0000 && code <= 0x007F) { // 單字節,這裏並不考慮0x0000,由於它是空字節 // U+00000000 – U+0000007F 0xxxxxxx res.push(str.charAt(i)); } else if (code >= 0x0080 && code <= 0x07FF) { // 雙字節 // U+00000080 – U+000007FF 110xxxxx 10xxxxxx // 110xxxxx // 0xC0 爲12*16 = 192 二進制爲 11000000 // 0x1F爲 31 二進制 00011111,由於第一個字節只取5位 // code 右移六位是由於從高位開始取得,因此須要將低位的六位留到第二個字節 var byte1 = 0xC0 | ((code >> 6) & 0x1F); // 10xxxxxx // 0x80爲128 二進制爲 10000000 // 0x3F爲63 二進制位 00111111,由於只須要取到低位的6位 var byte2 = 0x80 | (code & 0x3F); res.push(String.fromCharCode(byte1), String.fromCharCode(byte2)); } else if (code >= 0x0800 && code <= 0xFFFF) { // 三字節 // U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx // 1110xxxx // 0xE0 爲224 二進制爲 11100000 // 同理,須要留下 12位給低位 // 0x0F爲15 00001111 var byte1 = 0xE0 | ((code >> 12) & 0x0F); // 10xxxxxx // 再留6位給低位 var byte2 = 0x80 | ((code >> 6) & 0x3F); // 10xxxxxx var byte3 = 0x80 | (code & 0x3F); res.push(String.fromCharCode(byte1), String.fromCharCode(byte2), String.fromCharCode(byte3)); } else if (code >= 0x00010000 && code <= 0x001FFFFF) { // 四字節 // U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx // 同理,須要留下 18位給低位 // 0x07 00000111 // 0xF0 240 11110000 var byte1 = 0xF0 | ((code >> 18) & 0x07); // 10xxxxxx // 再留12位給低位 var byte2 = 0x80 | ((code >> 12) & 0x3F); // 再留6位給低位 var byte3 = 0x80 | ((code >> 6) & 0x3F); // 10xxxxxx var byte4 = 0x80 | (code & 0x3F); res.push(String.fromCharCode(byte1), String.fromCharCode(byte2), String.fromCharCode(byte3), String.fromCharCode(byte4)); } else if (code >= 0x00200000 && code <= 0x03FFFFFF) { // 五字節 // U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx // 同理,須要留下 24位給低位 // 0x03 00000011 // 0xF8 248 11111000 var byte1 = 0xF8 | ((code >> 24) & 0x03); // 10xxxxxx // 再留18位給低位 var byte2 = 0x80 | ((code >> 18) & 0x3F); // 再留12位給低位 var byte3 = 0x80 | ((code >> 12) & 0x3F); // 再留6位給低位 var byte4 = 0x80 | ((code >> 6) & 0x3F); // 10xxxxxx var byte5 = 0x80 | (code & 0x3F); res.push(String.fromCharCode(byte1), String.fromCharCode(byte2), String.fromCharCode(byte3), String.fromCharCode(byte4), String.fromCharCode(byte5)); } else /** if (code >= 0x04000000 && code <= 0x7FFFFFFF)*/ { // 六字節 // U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx // 同理,須要留下 24位給低位 // 0x01 00000001 // 0xFC 252 11111100 var byte1 = 0xFC | ((code >> 30) & 0x01); // 10xxxxxx // 再留24位給低位 var byte2 = 0x80 | ((code >> 24) & 0x3F); // 再留18位給低位 var byte3 = 0x80 | ((code >> 18) & 0x3F); // 再留12位給低位 var byte4 = 0x80 | ((code >> 12) & 0x3F); // 再留6位給低位 var byte5 = 0x80 | ((code >> 6) & 0x3F); // 10xxxxxx var byte6 = 0x80 | (code & 0x3F); res.push(String.fromCharCode(byte1), String.fromCharCode(byte2), String.fromCharCode(byte3), String.fromCharCode(byte4), String.fromCharCode(byte5), String.fromCharCode(byte6)); } } return res.join(''); };
Step1:用該碼的二進制和相應的關鍵字節相與,根據轉換關係表,判斷處於那一段區間,來判斷是使用幾個字節存儲字符的,而後分別合併對應的字節數,組成新的字符輸出。
/** * @description UTF8編碼字符串轉爲UTF16編碼字符串 * @param {String} str utf8編碼的字符串 * @return {String} utf16編碼的字符串,能夠直接被js用來打印 */ exports.utf8StrToUtf16Str = function(str) { if (!str) { return ''; } // res是用來存放結果的字符數組,最終會轉爲字符串返回 var res = [], len = str.length; for (var i = 0; i < len; i++) { // 得到對應的unicode碼 var code = str.charCodeAt(i); // 對第一個字節進行判斷 if (((code >> 7) & 0xFF) == 0x0) { // 0xFF 255 11111111,表明只取前8位 // 右移7位,若是是隻剩下0了,表明這個是單字節 // 單字節 // 0xxxxxxx res.push(str.charAt(i)); } else if (((code >> 5) & 0xFF) == 0x6) { // 雙字節 110開頭 // 110xxxxx 10xxxxxx // 須要用到下一個字節 var code2 = str.charCodeAt(++i); // 0x1F 31 00011111 // 取到第一個字節的後5位,而後左移6位(這6位留給第二個字節的低6位),因爲js是number型,因此沒必要擔憂溢出 var byte1 = (code & 0x1F) << 6; // 0x3F 63 00111111 var byte2 = code2 & 0x3F; // 或運算,由於第一個字節第六位沒有,第二個字節只有低6位,因此算是結合了 var utf16 = byte1 | byte2; res.push(String.fromCharCode(utf16)); } else if (((code >> 4) & 0xFF) == 0xE) { // 三字節 1110開頭 // 1110xxxx 10xxxxxx 10xxxxxx var code2 = str.charCodeAt(++i); var code3 = str.charCodeAt(++i); // 和00001111與後, 左移12位 var byte1 = (code & 0x0F) << 12; // 和00111111與後,左移6位 var byte2 = (code2 & 0x3F) << 6; // 和00111111與 var byte3 = code3 & 0x3F var utf16 = byte1 | byte2 | byte3; res.push(String.fromCharCode(utf16)); } else if (((code >> 3) & 0xFF) == 0x1E) { // 四字節 11110開頭 // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx var code2 = str.charCodeAt(++i); var code3 = str.charCodeAt(++i); var code4 = str.charCodeAt(++i); // 和00000111與後, 左移18位 var byte1 = (code & 0x07) << 18; // 和00111111與後,左移12位 var byte2 = (code2 & 0x3F) << 12; // 和00111111與後,左移6位 var byte3 = (code3 & 0x3F) << 6; // 和00111111與 var byte4 = code4 & 0x3F var utf16 = byte1 | byte2 | byte3 | byte4; res.push(String.fromCharCode(utf16)); } else if (((code >> 2) & 0xFF) == 0x3E) { // 五字節 111110開頭 // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx var code2 = str.charCodeAt(++i); var code3 = str.charCodeAt(++i); var code4 = str.charCodeAt(++i); var code5 = str.charCodeAt(++i); // 和00000011與後, 左移24位 var byte1 = (code & 0x03) << 24; // 和00111111與後,左移18位 var byte2 = (code2 & 0x3F) << 18; // 和00111111與後,左移12位 var byte3 = (code3 & 0x3F) << 12; // 和00111111與後,左移6位 var byte4 = (code4 & 0x3F) << 6; // 和00111111與 var byte5 = code5 & 0x3F var utf16 = byte1 | byte2 | byte3 | byte4 | byte5; res.push(String.fromCharCode(utf16)); } else /** if (((code >> 1) & 0xFF) == 0x7E)*/ { // 六字節 1111110開頭 // 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx var code2 = str.charCodeAt(++i); var code3 = str.charCodeAt(++i); var code4 = str.charCodeAt(++i); var code5 = str.charCodeAt(++i); var code6 = str.charCodeAt(++i); // 和00000001與後, 左移30位 var byte1 = (code & 0x01) << 30; // 和00111111與後,左移24位 var byte2 = (code2 & 0x3F) << 24; // 和00111111與後,左移18位 var byte3 = (code3 & 0x3F) << 18; // 和00111111與後,左移12位 var byte4 = (code4 & 0x3F) << 12; // 和00111111與後,左移6位 var byte5 = (code5 & 0x3F) << 6; // 和00111111與 var byte6 = code6 & 0x3F var utf16 = byte1 | byte2 | byte3 | byte4 | byte5 | byte6; res.push(String.fromCharCode(utf16)); } } return res.join(''); };
注意,這裏爲了篇幅,沒有將GBK和Unicode的碼錶映射放進來,更多詳細能夠參考博文中的源碼。
Step2:判斷該Unicode的範圍
/** * @description 將utf16編碼的字符串(js內置編碼)轉爲GBK編碼的字符串 * @param {String} str utf16編碼的字符串(js內置) * @return {String} 轉換後gbk編碼的字符串 */ exports.utf16StrToGbkStr = function(str) { if (!str) { return ''; } // res是用來存放結果的字符數組,最終會轉爲字符串返回 var res = [], len = str.length; for (var i = 0; i < len; i++) { // 得到對應的unicode碼 var code = str.charCodeAt(i); if (code < 0) { code += 65536; } if (code > 127) { code = unicode2GBKCode(code); } if (code > 255) { // gbk中,若是是漢字的,須要兩位來表示 // 對所收錄字符進行了「分區」處理,分爲若干區,每區若干碼位 // 第一個字節爲「高字節」,對應不一樣分區 // 第二個字節爲「低字節」,對應每一個區的不一樣碼位 var varlow = code & 65280; // 取得低位 varlow = varlow >> 8; // 取得高位 var varhigh = code & 255; res.push(String.fromCharCode(varlow)); res.push(String.fromCharCode(varhigh)); } else { res.push(String.fromCharCode(code)); } } return res.join(''); }; /** * @description 將unicode經過查錶轉換,轉爲gbk的code * @param {Number} chrCode 字符unicode編碼 */ function unicode2GBKCode(chrCode) { var chrHex = chrCode.toString(16); chrHex = "000" + chrHex.toUpperCase(); chrHex = chrHex.substr(chrHex.length - 4); var i = unicodeCharTable.indexOf(chrHex); if (i != -1) { chrHex = gbkCharTable.substr(i, 4); } return parseInt(chrHex, 16) };
Step2:判斷該Unicode的範圍
/** * @description 將GBK編碼的字符串轉爲utf16編碼的字符串(js內置編碼) * @param {String} str GBK編碼的字符串 * @return {String} 轉化後的utf16字符串 */ exports.gbkStrToUtf16Str = function(str) { if (!str) { return ''; } // res是用來存放結果的字符數組,最終會轉爲字符串返回 var res = [], len = str.length; for (var i = 0; i < len; i++) { // 得到對應的unicode碼 var code = str.charCodeAt(i); // 若是不是ASCII碼 if (code > 127) { // 轉爲unicode // 這裏左移8位是由於編碼時,被右移了8位 code = gbkCode2Unicode((code << 8) + str.charCodeAt(++i)); } else { // 普通的ASCII碼,什麼都不作 } res.push(String.fromCharCode(code)); } return res.join(''); }; /** * @description將 gbk的對應的code經過查錶轉換,轉爲unicode * @param {Number} chrCode gbk字符對應的編碼 */ function gbkCode2Unicode(chrCode) { //以16進制形式輸出字符串 var chrHex = chrCode.toString(16); // chrHex = "000" + chrHex.toUpperCase(); // chrHex = chrHex.substr(chrHex.length - 4); var i = gbkCharTable.indexOf(chrHex); if (i != -1) { chrHex = unicodeCharTable.substr(i, 4); } return parseInt(chrHex, 16) };
爲了篇幅,如GBK何Unicode的碼錶映射沒有直接放在文中,詳細能夠參考源碼: https://github.com/dailc/charset-encoding-series
初次發佈2017.06.03
於我的博客
http://www.dailichun.com/2017/06/03/utf8ToUtf16ToGbk.html