今天團隊小夥伴給了我一個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 部份內容參考自阮老師文章