需求
源库中的数据是以BLOB的形式存储的,且数据中含有中文,MySQL数据库的字符集为utf8,最终想要的效果就是在浏览器中以文本的形式展示源库中的数据。为了实现这一需求,尝试了2种方案:
- 从Oracle层面解决,通过视图将相关字段转换成
VARCHAR2类型后在返回,这样从Oracle中查询数据的时候,直接拿到的就是字符串类型的数据。这样做的弊端是:Oracle数据库VARCHAR2类型最大只能支持4kb,如果超过了这个大小就会出错。 - 从Oracle取到数据后,使用Node.js转换成字符串后再存入到MySQL数据库中。
我使用了第2种解决方案,但是过程并不是很顺利。
遇到的问题
从Oracle数据库中取到的数据,在Node.js中是Buffer对象,要将Buffer对象转换成字符串对Node.js来说实在是太常规了,直接buffer.toString就完事了,可事实并非如此,得到的字符串都是乱码。一般遇到这个问题,大家的第一反应肯定是编码问题,我也是这么想的,考虑到数据中有中文,而Node.js原生并没有支持中文的相关编码,默认是utf8,已经尝试过了。所以就引入了iconv-lite这个模块,用来对Buffer对象进行解码,但是Oracle中使用的字符集是SIMPLIFIED CHINESE_CHINA.ZHS32GB18030,所以我想当然的就使用GB18030编码来解码,代码示例:
const iconv = require('iconv-lite');
// Convert from an encoded buffer to js string.
const str = iconv.decode(buffer, 'gb18030');
结果得到的字符串还是乱码,然后我又把iconv-lite支持的所有中文编码又试了一遍,得到的字符串全都是乱码。
解决
经过一番Google和尝试后仍然没有解决,然后就在上述提到的两种方案之间来回折腾。后来在朋友的引导下,得到了一个思路:先探测Buffer对象的编码,得到确定的编码后,再进行解码。于是乎就找到了这个模块:detect-character-encoding。这个模块主要是用来探测字符编码的,使用方法也很简单,示例代码:
const fs = require('fs');
const detectCharacterEncoding = require('detect-character-encoding');
const fileBuffer = fs.readFileSync('file.txt');
const charsetMatch = detectCharacterEncoding(fileBuffer);
console.log(charsetMatch);
// {
// encoding: 'UTF-8',
// confidence: 60
// }
于是乎就用这个模块对上述提到的Buffer对象进行探测,得到的编码竟然是UTF-16LE,然后使用这个编码进行解码,果然得到了正确的字符串。问题到此彻底解决了。
注意事项
- 探测编码时请多用一些数据样例来探测,最后使用可信度最高的编码。
- 千万不要动态探测编码,然后动态解码,因为这个模块的探测结果是随着数据的变化而变化的。
- 使用iconv-lite模块解码时,如果编码名称中有字母,请一律使用小写字母。
- 一定要确保从Oracle取到的数据在Node.js环境中为Buffer对象。
其他说明
总结
这次遇到的问题,其实解决方案是比较清晰的,但是在对Buffer进行解码遇到问题后没有冷静下来分析,在2个解决方案之间来回折腾浪费了很多时间;当已经很明确问题出现在哪个环节时,应该借助相关工具进一步确认问题的根源所在,比如:这次在解码环节出现了问题,而问题的根源也比较清晰,就是解码时使用的编码不对,所以就应该先明确Buffer对象所使用的编码,然后再用正确的编码进行解码即可。
UTF-16LE 就是
ucs2啊Buffer.toString()默认参数是Buffer.toString('utf8'), 你这个需求无非是显式指定输出编码buf.toString('ucs2')官方文档没仔细看吧~~其实用 buf.slice(10), 截取前面几个块看看(基本)就知道是啥编码了
00有规律间隔出现(假定数据里面包含多个数字或者拉丁字母),那么极大可能性是ucs2utf8或者双字节编码gbk之类ascii编码base64编码@waitingsong 感谢大佬指出,我对编码不太熟,所以看到打印的buffer也不太敏感。
http://nodejs.cn/api/buffer.html
Node.js 支持的字符编码有:
不支持 UTF16BE,所以看到间隔出现的
00基本上就是 UTF16LE 了@waitingsong 感谢科普。
@waitingsong 哇,GET
来自酷炫的 CNodeMD
@waitingsong 大佬,像这样的能看出编码么?试了好多种编码解出来都是乱码。
不是UTF8,不是GBK。是否多层包装编码?
@waitingsong 不知道,昨天试了很多种编码来解码,都不行。目前无解。。。多层包装编码是什么意思?
@blackmatch 比如原文是 utf8 编码,第一次包装成 gbk,然后第二次包装成 ucs…
你的代码没有经过开光,所以有BUG
@waitingsong 可能是,客户不告诉我们是怎么包装的,说这是商业机密。。。。所以现在很难解码。
@blackmatch 这是加密数据? 如果让你们处理数据然后又不告诉解码方式,这啥操作?