今天团队小伙伴给了我一个json
配置文件,能够用以下替代(毕竟内容不是重点):php
{ "text": "this is a example" }
考虑到这个json
并不须要常驻,就没有用require
来引用,由于node
模块的缓存机制,势必会致使内存泄漏问题的发生,就采起了如下方式:html
fs.readFile(`${__dirname}/y.json`, 'utf8', function(err, str) { if (err) { throw err; } try { const data = JSON.parse(str); // ... } catch(err) { throw err; } });
可是诡异的事情发生了,JSON.parse
居然报错了???node
Unexpected token in JSON at position 0
此时一脸懵逼,就用了require
的方式试了一下发现一点问题都没有,考虑到了团队小伙伴使用的windows
,就去问了下他,得知这个json
用notepad++
写的,加上以前写php
常常遇到的BOM
问题,就猜想这个bug由BOM
引发,将读出来的str
转成Buffer
来看果真开头是ef bb bf
。下面先来看下今天说的这个BOM
究竟是个什么东西:python
字节顺序标记(英语:byte-order mark,BOM)是位于码点U+FEFF的统一码字符的名称。当以UTF-16或UTF-32来将UCS/统一码字符所组成的字符串编码时,这个字符被用来标示其字节序。它常被用来当作标示文件是以UTF-八、UTF-16或UTF-32编码的记号。json
说白了就是存在于文本文件的开头,标记出文件是依靠那种格式进行编码的,mac
上应该不存在,可是windows
的notepad++
通常会带有。你们也能够用python
写一个带有BOM
标记的文件,来验证这个问题:windows
import codecs code = '''{ "x": 20 } ''' f = codecs.open('y.json', 'w', 'utf_8_sig') f.write(code) f.close()
了解了产生缘由以及BOM
究竟是什么,还有一个疑惑就是为何用require
引入能够?缓存
记得require
是用的fs.readFileSync
同步读取的,为何这个能够呢?猜想都是无用的,来看下node
的源码,找到了这段:app
Module._extensions['.json'] = function(module, filename) { var content = fs.readFileSync(filename, 'utf8'); try { module.exports = JSON.parse(internalModule.stripBOM(content)); } catch (err) { err.message = filename + ': ' + err.message; throw err; } };
看了上面的代码能够很是明了,require
在读取以后,对字符串进行了去除BOM的操做,来看下internalModule.stripBOM
的实现:ui
function stripBOM(content) { // 检测第一个字符是否为BOM if (content.charCodeAt(0) === 0xFEFF) { content = content.slice(1); } return content; }
至此问题已经解决了,可是我还有一点不明白的是ef bb bf
为utf8
的标记,为何会转换为feff
,这个不是utf16
大端序的表示吗?下面就来解决这个疑惑:this
先来说一下编码的历史,首先出现的表示字符编码为ASCII
,八位二进制,能够表示出256
种状态,英文用128
个符号编码就能够了,可是其余的语言却没法表示,因而在一些欧洲国家,开始各自规定其表示,好比130在法语表明一个字符,俄语表明一个字符,这样形成了0-127
一致,而128-255
可能会千差万别;为了解决这种问题,国际组织设计提出了Unicode
,一个能够容纳全世界全部语言文字的编码方案,Unicode
只规定了符号的二进制代码,可是没有规定该如何存储,好比中文可能至少须要2个字节,而英文只须要一个字节便可。utf8
做为一种Unicode
的实现方式被普遍颚用于互联网应用中,utf8
明确了编码规则:
对于单字节的符号,将其第一位置为0,使用后面7位进行表示,因此说英文utf8
编码与ASCII
码一致
对于n(n > 2)个字节的符号,第一个字节的前n为都设置为1,第n+1为设为0,后面字节的前两位一概设为10,剩下的二进制位,为这个符号的Unicode
码
能够参见如下对照:
字符字节 | Unicode符号范围 | utf8编码方式 |
---|---|---|
1 | 0000 0000 - 0000 007F | 0xxxxxxx |
2 | 0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx |
3 | 0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
4 | 0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
5 | 0020 0000 - 03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
6 | 0400 0000 - 7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
来看下feff
转化为ef bb bf
,fs.readFileSync
进行了buffer -> string
的转换,buffer
的编码为utf8
,而string
为Unicode
,根据上表计算下:
F | E | F | F |
---|---|---|---|
1111 | 1110 | 1111 | 1111 |
根据其范围,得出其utf8编码:
1110 | 1111 | 1011 | 1011 | 1011 | 1111 |
---|---|---|---|---|---|
E | F | B | B | B | F |
用代码来实现下Unicode
转utf8
的过程:
def UnicodeToUtf8(unic): res = list() if unic < 0x7F: res.append(hex(unic & 0x7F)) elif unic >= 0x80 and unic <= 0x7FF: # 110xxxxx res.append(((unic >> 6) & 0x1F) | 0xC0) # 10xxxxxx res.append((unic & 0x3F) | 0x80) elif unic >= 0x800 and unic <= 0xFFFF: # 1110xxxx res.append(((unic >> 12) & 0x0F) | 0xE0) # all is 10xxxxxx res.append(((unic >> 6) & 0x3F) | 0x80) res.append((unic & 0x3F) | 0x80) elif unic >= 0x10000 and unic <= 0x1FFFFF: # 11110xxx res.append(((unic >> 18) & 0x07) | 0xF0) # all is 10xxxxxx res.append(((unic >> 12) & 0x3F) | 0x80) res.append(((unic >> 6) & 0x3F) | 0x80) res.append((unic & 0x3F) | 0x80) elif unic >= 0x200000 and unic <= 0x3FFFFFF: # 111110xx res.append(((unic >> 24) & 0x03) | 0xF8) # all is 10xxxxxx res.append(((unic >> 18) & 0x3F) | 0x80) res.append(((unic >> 12) & 0x3F) | 0x80) res.append(((unic >> 6) & 0x3F) | 0x80) res.append((unic & 0x3F) | 0x80) elif unic >= 0x4000000 and unic <= 0x7FFFFFFF: # 1111110x res.append(((unic >> 30) & 0x01) | 0xFC) # all is 10xxxxxx res.append(((unic >> 24) & 0x3F) | 0x80) res.append(((unic >> 18) & 0x3F) | 0x80) res.append(((unic >> 12) & 0x3F) | 0x80) res.append(((unic >> 6) & 0x3F) | 0x80) res.append((unic & 0x3F) | 0x80) return map(lambda r:hex(r), res) # test print UnicodeToUtf8(0xFEFF)
utf8
转Unicode
只须要去除标志位便可,这里就不在实现。
到此,终于清楚的能够和团队小伙伴说出bug的解决方法就利用上面的stripBOM
若有错误,还请指出!
Unicode与utf8 部份内容参考自阮老师文章