JS字符串轉(zhuǎn)GBK編碼超精簡(jiǎn)實(shí)現(xiàn)詳解
前言
JS 中 GBK 編碼轉(zhuǎn)字符串是非常簡(jiǎn)單的,直接調(diào)用 TextDecoder 即可:
const gbkBuf = new Uint8Array([196, 227, 186, 195, 49, 50, 51])
new TextDecoder('gbk').decode(gbkBuf) // "你好123"
但反過(guò)來(lái),字符串轉(zhuǎn) GBK 編碼卻沒(méi)這么簡(jiǎn)單,因?yàn)?nbsp;TextEncoder 無(wú)法指定字集,只能將字符串轉(zhuǎn)成 UTF-8 編碼的二進(jìn)制數(shù)據(jù)。
因此業(yè)內(nèi)絕大多數(shù)的解決方案都是使用第三方編碼庫(kù),例如 iconv。由于這些庫(kù)打包了大量字集數(shù)據(jù),體積非??捎^,即便是精簡(jiǎn)版的 iconv-lite 也有幾百 kB,這在瀏覽器端顯然很不完美。我們希望只用幾百字節(jié)就能解決!
遍歷
查閱資料可得,GBK 其實(shí)只有兩萬(wàn)多個(gè)字符,因此最簡(jiǎn)單的辦法就是「暴力窮舉」。借助 TextDecoder 可遍歷出每個(gè) GBK 對(duì)應(yīng)的 JS 字符,之后的編碼過(guò)程無(wú)非就是查表而已。
事實(shí)上 GBK 的編碼范圍是有規(guī)律的:

https://en.wikipedia.org/wiki/GBK_(character_encoding)#Encoding
因此只需在預(yù)定范圍中遍歷,即使多花十幾行代碼但能提高性能,也是值得的。
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]
}
如果每遍歷一個(gè) GBK 就調(diào)用一次 TextDecoder,那顯然是十分低效的。因此我們將所有 GBK 集中存放在上述 codes 數(shù)組中,最后只調(diào)用一次 TextDecoder 批量轉(zhuǎn)換。
這個(gè)初始化過(guò)程只需 1ms ~ 2ms,開(kāi)銷非常低。
查表
有了映射表,編碼時(shí)直接查表即可:
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]
輸出結(jié)果和本文開(kāi)頭演示的一致。
不過(guò)上述忽略了 ASCII 范圍,如果傳入「你好123」就有問(wèn)題了。由于 GBK 的 ASCII 部分是單字節(jié)存儲(chǔ)的,因此編碼邏輯需調(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]
輸出結(jié)果和本文開(kāi)頭演示的一致。
出于性能考慮,這里使用 Uint8Array 而不是 Array。但 Uint8Array 長(zhǎng)度是固定的,申請(qǐng)后不能改變,因此假設(shè)輸入的字符串中都是非 ASCII 字符,從而確保緩沖區(qū)充足,最后返回時(shí)再截取。(使用 subarray 引用,無(wú)需復(fù)制)
完善
如果編碼時(shí)傳入了 GBK 不支持的字符,按上述邏輯將會(huì)變成 0 字符,因?yàn)?table 空缺位置默認(rèn)為 0。而 0 本身也是 GBK 的一部分,因此并不完善。
因此我們可將 table 填充成其他值,之后查表時(shí)出現(xiàn)該值,可作為異常處理。
此外根據(jù)百科上科普,微軟基于 GBK 實(shí)現(xiàn)的 Code page 936 多一個(gè) 0x80 字碼,對(duì)應(yīng)的字符是歐元符號(hào) €。
試了下,即使非 Windows 系統(tǒng)的瀏覽器也支持:
const gbkBuf = new Uint8Array([0x80])
new TextDecoder('gbk').decode(gbkBuf) // "€"
演示:https://jsbin.com/vuxawul/edit?html,output
最終實(shí)現(xiàn):https://github.com/EtherDream/str2gbk
使用這種方案,幾十行代碼幾百字節(jié)就能實(shí)現(xiàn) GBK 編碼,并且性能非常高。
以上就是JS字符串轉(zhuǎn)GBK編碼超精簡(jiǎn)實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于JS字符串轉(zhuǎn)GBK編碼的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于PHP pthreads實(shí)現(xiàn)多線程代碼實(shí)例
這篇文章主要介紹了基于PHP pthreads實(shí)現(xiàn)多線程代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
IE本地存儲(chǔ)userdata的一個(gè)bug說(shuō)明
再一次項(xiàng)目上用到ie的userdata,考慮當(dāng)用戶不使用flash插件用于存儲(chǔ)一些聊天記錄2010-07-07
js實(shí)現(xiàn)帶三角符的手風(fēng)琴效果
本文主要介紹了js實(shí)現(xiàn)帶三角符手風(fēng)琴效果的實(shí)例。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-03-03
JS循環(huán)遍歷JSON數(shù)據(jù)的方法
這篇文章主要介紹了JS 循環(huán)遍歷JSON數(shù)據(jù)的方法,需要的朋友可以參考下2014-07-07
Add Formatted Data to a Spreadsheet
Add Formatted Data to a Spreadsheet...2007-06-06
layui當(dāng)點(diǎn)擊文本框時(shí)彈出選擇框,顯示選擇內(nèi)容的例子
今天小編就為大家分享一篇layui當(dāng)點(diǎn)擊文本框時(shí)彈出選擇框,顯示選擇內(nèi)容的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09
JS實(shí)現(xiàn)帶動(dòng)畫(huà)的回到頂部效果
這篇文章主要為大家詳細(xì) 介紹了JS實(shí)現(xiàn)帶動(dòng)畫(huà)的回到頂部效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
javascript中的with語(yǔ)句學(xué)習(xí)筆記及用法
在本篇文章里小編給大家分享的是關(guān)于javascript中的with語(yǔ)句學(xué)習(xí)筆記及用法,有需要的朋友們可以學(xué)習(xí)下。2020-02-02

