前言

JS 中 GBK 编码转字符串是非常简单的,直接调用 TextDecoder 即可:

const gbkBuf = new Uint8Array([196, 227,   186, 195,   49, 50, 51])
new TextDecoder('gbk').decode(gbkBuf) // "你好123"

但反过来,字符串转 GBK 编码却没这么简单,因为 TextEncoder 无法指定字集,只能将字符串转成 UTF-8 编码的二进制数据。

因此业内绝大多数的解决方案都是使用第三方编码库,例如 iconv。由于这些库打包了大量字集数据,体积非常可观,即便是精简版的 iconv-lite 也有几百 kB,这在浏览器端显然很不完美。我们希望只用几百字节就能解决!

遍历

查阅资料可得,GBK 其实只有两万多个字符,因此最简单的办法就是「暴力穷举」。借助 TextDecoder 可遍历出每个 GBK 对应的 JS 字符,之后的编码过程无非就是查表而已。

事实上 GBK 的编码范围是有规律的:

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 就调用一次 TextDecoder,那显然是十分低效的。因此我们将所有 GBK 集中存放在上述 codes 数组中,最后只调用一次 TextDecoder 批量转换。

这个初始化过程只需 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 部分是单字节存储的,因此编码逻辑需调整:

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 字符,从而确保缓冲区充足,最后返回时再截取。(使用 subarray 引用,无需复制)

完善

如果编码时传入了 GBK 不支持的字符,按上述逻辑将会变成 0 字符,因为 table 空缺位置默认为 0。而 0 本身也是 GBK 的一部分,因此并不完善。

因此我们可将 table 填充成其他值,之后查表时出现该值,可作为异常处理。

此外根据百科上科普,微软基于 GBK 实现的 Code page 936 多一个 0x80 字码,对应的字符是欧元符号

试了下,即使非 Windows 系统的浏览器也支持:

const gbkBuf = new Uint8Array([0x80])
new TextDecoder('gbk').decode(gbkBuf) // "€"

演示:https://jsbin.com/vuxawul/edit?html,output

最终实现:https://github.com/EtherDream/str2gbk

使用这种方案,几十行代码几百字节就能实现 GBK 编码,并且性能非常高。

JS 字符串转 GBK 编码超精简实现的更多相关文章

  1. js字符串与Unicode编码互相转换

    ).toString() "597d" 这段代码的意思是,把字符'好'转化成Unicode编码,toString()就是把字符转化成16进制了 看看charCodeAt()是怎么个 ...

  2. js字符串三个编码的区别

    1.escape():编码目的为了防止字符串中特殊字符造成运算错误,主要在字符串运算中使用: 不进行编码的69个字符:A-Z.a-z.0-9.@.*._.+.-...\. 2.encodeURI(): ...

  3. 使用js对中文进行gbk编码

    使用js对中文进行gbk编码 分类: JS/JQUERY2013-02-09 11:29 436人阅读 评论(0) 收藏 举报 最近遇到一个问题,需要通过js来从一个utf-8编码的页面传递一个含中文 ...

  4. Node.js转化GBK编码 - iconv-lite

    node当使用node获取GBK编码的数据时,nodejs仅仅支持utf-8,node没有提供转换编码的原生支持,有倒是有一个模块iconv能干这个事,但须要本地方法,VC++库的支持.国外有个大牛写 ...

  5. 浅谈 js 字符串之神奇的转义

    原文:浅谈 js 字符串之神奇的转义 字符串在js里是非常常用的,但是你真的了解它么?翻阅<MDN String>就可以了解它的常见用法了,开门见山的就让你了解了字符串是怎么回事. 'st ...

  6. php和js中,utf-8编码转成base64编码

    1.php下转化base64编码 php中,文本文件的编码决定了程序变量的编码,比如以下代码在不同编码的php文件中,展示的效果也是不一样的 <?php $word = '严'; echo ba ...

  7. js验证身份证号,超准确

    js验证身份证号,超准确 看程序前先来了解下身份证号的构成:身份证号分为两种,旧的为15位,新的为18位.身份证15位编码规则:dddddd yymmdd xx p    其中 dddddd:地区码  ...

  8. Python中的字符串与字符编码

    本节内容: 前言 相关概念 Python中的默认编码 Python2与Python3中对字符串的支持 字符编码转换 一.前言 Python中的字符编码是个老生常谈的话题,同行们都写过很多这方面的文章. ...

  9. GBK 编码时 url 中带中文参数的问题

    项目中遇到的 GBK 编码问题,记录如下. 将代码精简为: <!DOCTYPE HTML> <html> <meta charset="gb2312" ...

随机推荐

  1. 好客租房21-react组件的两种创建方式(函数组件)

    1使用函数创建组件 函数组件:使用js的函数或者箭头函数创建的组件 约定1:函数组件名称必须以 开头 约定2:函数组件必须有返回值 表示该组件的结构 如果返回值为null 表示不渲染任何内容 2.1使 ...

  2. Git技法:.gitignore、移除暂存与撤销修改

    1. .gitignore常见项目添加 1.1 .gitignore模板 .gitignore针对每个语言都有对应的模板,在GitHub创建项目时就可以选择(你可以在GitHub提供的.gitigno ...

  3. 微前端(qiankun)主应用共享React组件

    前言 最近需要重构一个老项目,定的方案用微前端去改造.主应用是老的项目,微应用是新的项目,由于重构时间比较紧张,子应用还需要使用父应用的一些组件.过程中遇到一些问题,记录一下. 方案 我们知道qian ...

  4. CabloyJS一站式助力微信、企业微信、钉钉开发 - 企业微信篇

    前言 现在软件开发不仅要面对前端碎片化,还要面对后端碎片化.针对前端碎片化,CabloyJS提供了pc=mobile+pad的跨端自适应方案,参见:自适应布局:pc = mobile + pad 在这 ...

  5. conda创建/移除虚拟环境

    conda创建python虚拟环境 前言 conda常用的命令: conda list 查看安装了哪些包. conda env list 或 conda info -e 查看当前存在哪些虚拟环境 co ...

  6. .NET中线程锁的使用

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年7月1日. 一.说明 由于经常需要在多线程代码中使用Monitor进行同步,并且需要自己去手写try/finally块.因此C#提供了 ...

  7. 合宙AIR105(二): 时钟设置和延迟函数

    目录 合宙AIR105(一): Keil MDK开发环境, DAP-Link 烧录和调试 合宙AIR105(二): 时钟设置和延迟函数 Air105 的时钟 高频振荡源 芯片支持使用内部振荡源, 或使 ...

  8. mysql调优学习笔记

    性能监控 使用show profile查询剖析工具,可以指定具体的type 此工具默认是禁用的,可以通过服务器变量在绘画级别动态的修改 set profiling=1; 当设置完成之后,在服务器上执行 ...

  9. 17.Nginx 重写(location rewrite)

    Nginx 重写(location / rewrite) 目录 Nginx 重写(location / rewrite) 常见的nginx正则表达式 location lication的分类 loca ...

  10. 循环码、卷积码及其python实现

    摘要:本文介绍了循环码和卷积码两种编码方式,并且,作者给出了两种编码方式的编码译码的python实现 关键字:循环码,系统编码,卷积码,python,Viterbi算法 循环码的编码译码 设 \(C\ ...