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" ...
随机推荐
- 循环中的scanf处理了换行符怎么破
这种情况一般在循环中要求输入一个字符时容易出现问题. 问题在于缓冲区,缓冲区中存留了换行符,所以... 运行下面代码: int main(void){ char a=0; while(1){ scan ...
- 992. Sort Array By Parity II - LeetCode
Question 992. Sort Array By Parity II Solution 题目大意:给一个int数组,一半是奇数一半是偶数,分别对偶数数和奇数数排序并要求这个数本身是偶数要放在偶数 ...
- WPF 分组
分组和树形结构是不一样的. 树形结构是以递归形式存在.分组是以键值对存在的形式,类似于GroupBy这样的形式. 举个例子 ID NAME SEX Class 1 张三 男 1 2 李四 女 2 3 ...
- 【Azure Developer】App Service + PubSub +JS 实现多人版黑客帝国文字流效果图
需要描述 1)实现黑客帝国文字流效果图,JS功能 2)部署在云中,让大家都可以访问,App Service实现 3)大家都能发送消息,并显示在文字流中,PubSub(websocket)实现 终极效果 ...
- html5手册语义化标签
html5手册语义化标签: article section aside hgroup header footer nav time mark figure figcaption contextmenu ...
- 在 Traefik Proxy 2.5 中使用/开发私有插件(Traefik 官方博客)
Traefik Proxy 在设计上是一个模块化路由器,允许您将中间件放入您的路由中,并在请求到达预期的后端服务目的地之前对其进行修改. Traefik 内置了许多这样的中间件,还允许您以插件的形式加 ...
- 如何优化PlantUML流程图(时序图)
这篇文章用来介绍,如何画出好看的流程图. 1. 选择合适的组件 1.1 plantuml官方提供的组件 1.2 加载图片 1.2.1 加载本地图片 1.2.2 加载网络图片 1.2.3 图片资源 2. ...
- 架构师必备:系统容量现状checklist
正如飞机在起飞前,机长.副机长要过一遍checklist检查,确认没问题了才能起飞.楼主也整理了一个系统容量现状checklist,方便对照检查.本文搭配架构师必备:如何做容量预估和调优,食用更佳. ...
- Java创建数据库新建表及初始化表
方法一 package com.crt.openapi; import java.sql.DriverManager;import java.sql.ResultSet;import java.io. ...
- 如何优雅的使用MyBatis?
本文目录 什么是 MyBatis ? 映射器(mappers) typeAliases 类型别名减少类完全限制名的冗余 处理枚举类型 多行插入 重用 SQL 代码段,消除重复 字符串替换#{}和${ ...