JS字符串轉(zhuǎn)GBK編碼超精簡實現(xiàn)詳解
前言
JS 中 GBK 編碼轉(zhuǎn)字符串是非常簡單的,直接調(diào)用 TextDecoder
即可:
const gbkBuf = new Uint8Array([196, 227, 186, 195, 49, 50, 51]) new TextDecoder('gbk').decode(gbkBuf) // "你好123"
但反過來,字符串轉(zhuǎn) GBK 編碼卻沒這么簡單,因為 TextEncoder
無法指定字集,只能將字符串轉(zhuǎn)成 UTF-8 編碼的二進制數(shù)據(jù)。
因此業(yè)內(nèi)絕大多數(shù)的解決方案都是使用第三方編碼庫,例如 iconv。由于這些庫打包了大量字集數(shù)據(jù),體積非??捎^,即便是精簡版的 iconv-lite 也有幾百 kB,這在瀏覽器端顯然很不完美。我們希望只用幾百字節(jié)就能解決!
遍歷
查閱資料可得,GBK 其實只有兩萬多個字符,因此最簡單的辦法就是「暴力窮舉」。借助 TextDecoder
可遍歷出每個 GBK 對應的 JS 字符,之后的編碼過程無非就是查表而已。
事實上 GBK 的編碼范圍是有規(guī)律的:
https://en.wikipedia.org/wiki/GBK_(character_encoding)#Encoding
因此只需在預定范圍中遍歷,即使多花十幾行代碼但能提高性能,也是值得的。
const ranges = [ [0xA1, 0xA9, 0xA1, 0xFE], [0xB0, 0xF7, 0xA1, 0xFE], [0x81, 0xA0, 0x40, 0xFE], [0xAA, 0xFE, 0x40, 0xA0], [0xA8, 0xA9, 0x40, 0xA0], [0xAA, 0xAF, 0xA1, 0xFE], [0xF8, 0xFE, 0xA1, 0xFE], [0xA1, 0xA7, 0x40, 0xA0], ] const codes = new Uint16Array(23940) let i = 0 for (const [b1Begin, b1End, b2Begin, b2End] of ranges) { for (let b2 = b2Begin; b2 <= b2End; b2++) { if (b2 !== 0x7F) { for (let b1 = b1Begin; b1 <= b1End; b1++) { codes[i++] = b2 << 8 | b1 } } } } const str = new TextDecoder('gbk').decode(codes) // 編碼表 const table = new Uint16Array(65536) for (let i = 0; i < str.length; i++) { table[str.charCodeAt(i)] = codes[i] }
如果每遍歷一個 GBK 就調(diào)用一次 TextDecoder
,那顯然是十分低效的。因此我們將所有 GBK 集中存放在上述 codes 數(shù)組中,最后只調(diào)用一次 TextDecoder
批量轉(zhuǎn)換。
這個初始化過程只需 1ms ~ 2ms,開銷非常低。
查表
有了映射表,編碼時直接查表即可:
function stringToGbk(str) { const buf = new Uint16Array(str.length) for (let i = 0; i < str.length; i++) { const code = str.charCodeAt(i) buf[i] = table[code] } return new Uint8Array(buf.buffer) } stringToGbk('你好') // [196, 227, 186, 195]
輸出結果和本文開頭演示的一致。
不過上述忽略了 ASCII 范圍,如果傳入「你好123」就有問題了。由于 GBK 的 ASCII 部分是單字節(jié)存儲的,因此編碼邏輯需調(diào)整:
function stringToGbk(str) { const buf = new Uint8Array(str.length * 2) let n = 0 for (let i = 0; i < str.length; i++) { const code = str.charCodeAt(i) if (code < 0x80) { buf[n++] = code } else { const gbk = table[code] buf[n++] = gbk & 0xFF buf[n++] = gbk >> 8 } } return buf.subarray(0, n) } stringToGbk('你好123') // [196, 227, 186, 195, 49, 50, 51]
輸出結果和本文開頭演示的一致。
出于性能考慮,這里使用 Uint8Array 而不是 Array。但 Uint8Array 長度是固定的,申請后不能改變,因此假設輸入的字符串中都是非 ASCII 字符,從而確保緩沖區(qū)充足,最后返回時再截取。(使用 subarray 引用,無需復制)
完善
如果編碼時傳入了 GBK 不支持的字符,按上述邏輯將會變成 0 字符,因為 table 空缺位置默認為 0。而 0 本身也是 GBK 的一部分,因此并不完善。
因此我們可將 table 填充成其他值,之后查表時出現(xiàn)該值,可作為異常處理。
此外根據(jù)百科上科普,微軟基于 GBK 實現(xiàn)的 Code page 936 多一個 0x80 字碼,對應的字符是歐元符號 €
。
試了下,即使非 Windows 系統(tǒng)的瀏覽器也支持:
const gbkBuf = new Uint8Array([0x80]) new TextDecoder('gbk').decode(gbkBuf) // "€"
演示:https://jsbin.com/vuxawul/edit?html,output
最終實現(xiàn):https://github.com/EtherDream/str2gbk
使用這種方案,幾十行代碼幾百字節(jié)就能實現(xiàn) GBK 編碼,并且性能非常高。
以上就是JS字符串轉(zhuǎn)GBK編碼超精簡實現(xiàn)詳解的詳細內(nèi)容,更多關于JS字符串轉(zhuǎn)GBK編碼的資料請關注腳本之家其它相關文章!
相關文章
nodejs讀取本地中文json文件出現(xiàn)亂碼解決方法
在本篇文章中我們給大家分享了關于nodejs讀取本地中文json文件出現(xiàn)亂碼的解決方法,需要的朋友們可以學習下。2018-10-10微信小程序canvas.drawImage完全顯示圖片問題的解決
這篇文章主要介紹了微信小程序canvas.drawImage完全顯示圖片問題的解決,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11JS構造一個html文本內(nèi)容成文件流形式發(fā)送到后臺
本文通過實例代碼給大家介紹了JS構造一個html文本內(nèi)容成文件流形式發(fā)送到后臺的相關資料,需要的朋友可以參考下2018-07-07ajax讀取數(shù)據(jù)后使用jqchart顯示圖表的方法
這篇文章主要介紹了ajax讀取數(shù)據(jù)后使用jqchart顯示圖表的方法,涉及Ajax操作及圖表插件的相關使用技巧,需要的朋友可以參考下2015-06-06