JS 字符串转 GBK 编码超精简实现
前言
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 编码超精简实现的更多相关文章
- js字符串与Unicode编码互相转换
).toString() "597d" 这段代码的意思是,把字符'好'转化成Unicode编码,toString()就是把字符转化成16进制了 看看charCodeAt()是怎么个 ...
- js字符串三个编码的区别
1.escape():编码目的为了防止字符串中特殊字符造成运算错误,主要在字符串运算中使用: 不进行编码的69个字符:A-Z.a-z.0-9.@.*._.+.-...\. 2.encodeURI(): ...
- 使用js对中文进行gbk编码
使用js对中文进行gbk编码 分类: JS/JQUERY2013-02-09 11:29 436人阅读 评论(0) 收藏 举报 最近遇到一个问题,需要通过js来从一个utf-8编码的页面传递一个含中文 ...
- Node.js转化GBK编码 - iconv-lite
node当使用node获取GBK编码的数据时,nodejs仅仅支持utf-8,node没有提供转换编码的原生支持,有倒是有一个模块iconv能干这个事,但须要本地方法,VC++库的支持.国外有个大牛写 ...
- 浅谈 js 字符串之神奇的转义
原文:浅谈 js 字符串之神奇的转义 字符串在js里是非常常用的,但是你真的了解它么?翻阅<MDN String>就可以了解它的常见用法了,开门见山的就让你了解了字符串是怎么回事. 'st ...
- php和js中,utf-8编码转成base64编码
1.php下转化base64编码 php中,文本文件的编码决定了程序变量的编码,比如以下代码在不同编码的php文件中,展示的效果也是不一样的 <?php $word = '严'; echo ba ...
- js验证身份证号,超准确
js验证身份证号,超准确 看程序前先来了解下身份证号的构成:身份证号分为两种,旧的为15位,新的为18位.身份证15位编码规则:dddddd yymmdd xx p 其中 dddddd:地区码 ...
- Python中的字符串与字符编码
本节内容: 前言 相关概念 Python中的默认编码 Python2与Python3中对字符串的支持 字符编码转换 一.前言 Python中的字符编码是个老生常谈的话题,同行们都写过很多这方面的文章. ...
- GBK 编码时 url 中带中文参数的问题
项目中遇到的 GBK 编码问题,记录如下. 将代码精简为: <!DOCTYPE HTML> <html> <meta charset="gb2312" ...
随机推荐
- cefsharp + winform 内嵌网页的触屏输入焦点问题
原文 现象 我正在使用 cefsharp + winform 建立一个桌面程序用于显示网页.但程序启动后触屏点击网页中的输入框,使用键盘输入,文字输入不进去.win + D 最小化程序后,再恢复窗口才 ...
- AOSP查看当前要打进系统里的都有哪些包
发现问题: 修改系统时常常需要预置APK,这就要修改PRODUCT_PACKAGES这个变量,那么如何查看这个数组的值呢?我们可能直接在mk文件中去打印它,后来发现打印它只会显示继承关系(即继承自了哪 ...
- CSAPP 之 ShellLab 详解
前言 本篇博客将会详细介绍 CSAPP 之 ShellLab 的完成过程,实现一个简易(lou)的 shell.tsh 拥有以下功能: 可以执行外部程序 支持四个内建命令,名称和功能为: quit:退 ...
- 『忘了再学』Shell基础 — 17、预定义变量
目录 1.预定义变量$? 2.预定义变量$$和$! 我们之前说过,Shell中的变量不是按照变量值的类型来进行分类的,而是按照Linux系统中定义的变量类别来分类的. 预定义变量就是,事先把变量的名称 ...
- 小白excel初步使用2022.06.02
1.添加 对表格数据相加求和:在表示数据的那一列黄色表格下输入=SUM(D1:D5)或者alt+=或者SUMIF(D1:D5,">50")或者SUMIF(D1,D3:D7,1 ...
- redis客户端打不开 提示cannot connect to server dev.check log for details
我当前状况是redis客户端打不开,之前一直好好的(虽然作为一个程序员我不配说这句话,哈哈),但是我排查了我觉得所有的可能性,我用的阿里云的redis(还没有到购买到期时间),然后我就关闭了防火墙,你 ...
- SCSS 简要教程(常用指令与方法)
Sass是成熟.稳定.强大的CSS预处理器,而SCSS是Sass3版本当中引入的新语法特性,完全兼容CSS3的同时继承了Sass强大的动态功能. 特性概览 CSS书写代码规模较大的Web应用时,容易造 ...
- SAP 实例 8 HTML from the MIME Repository
REPORT demo_html_from_mime. CLASS mime_demo DEFINITION. PUBLIC SECTION. CLASS-METHODS main. PRIVATE ...
- 获取mybatis注解方式新增数据时非自增插入的主键
场景:插入数据的时候,获取不到非自增的主键.原因:对象中没有主键的值,插入后主键才有值. 解决方案:使用 @SelectKey @SelectKey中: statement是要运行的SQL语句,即查询 ...
- 使用vue实现排序算法演示动画
缘起 最近做的一个小需求涉及到排序,界面如下所示: 因为项目是使用vue的,所以实现方式很简单,视图部分不用管,本质上就是操作数组,代码如下: { // 上移 moveUp (i) { // 把位置i ...