前端編解碼

背景

因?yàn)橹形牡牟┐缶钇禄牛约霸缙谖募幋a的不統(tǒng)一,造成了現(xiàn)在可能碰到的文件編碼有gb2312藻三、gbk洪橘、gb18030utf-8趴酣、big5等梨树。因?yàn)榫幗獯a的知識(shí)比較底層和冷門(mén),一直以來(lái)我對(duì)這幾個(gè)編碼的認(rèn)知也很膚淺岖寞,很多時(shí)候也會(huì)疑惑編碼名到底是大寫(xiě)還是小寫(xiě)抡四,英文和數(shù)字之間是不是需要加“-”,規(guī)則到底是windows定的還是國(guó)家定的等等仗谆。

我膚淺的認(rèn)知如下:

編碼 說(shuō)明
gb2312 最早的簡(jiǎn)體中文編碼指巡,還有海外版的hz-gb-2312
big5 繁體中文編碼,主要用于臺(tái)灣地區(qū)隶垮。小時(shí)候有些繁體中文游戲亂碼藻雪,都是因?yàn)閎ig5編碼和gb2312編碼的識(shí)別混亂導(dǎo)致
gbk 簡(jiǎn)體+繁體,我就當(dāng)它是gb2312+big5狸吞,向下兼容勉耀,在解碼時(shí)我一般選擇該編碼,因?yàn)榇虻淖稚偬F:髞?lái)了解到便斥,這個(gè)就是windows幫中國(guó)“好心的”擴(kuò)展了中文編碼,致使編碼庫(kù)又多了個(gè)新成員
gb18030 gb家族的新版威始,向下兼容枢纠,國(guó)家標(biāo)準(zhǔn),現(xiàn)在中文軟件都理應(yīng)支持的編碼格式黎棠,文件解碼的新選擇
utf-8 不解釋了晋渺,國(guó)際化編碼標(biāo)準(zhǔn)镰绎,html現(xiàn)在最標(biāo)準(zhǔn)的編碼格式。注:windows上的文本編輯器用到的utf-8是帶BOM的

BOM

當(dāng)使用windows記事本保存文件的時(shí)候木西,編碼方式可以選擇ANSI(通過(guò)locale判斷畴栖,簡(jiǎn)體中文系統(tǒng)下是gb家族)、Unicode八千、UTF-8等驶臊。那文件打開(kāi)的時(shí)候,系統(tǒng)是如何判斷該使用哪種編碼方式呢叼丑?

答案是:windows(例如:簡(jiǎn)體中文系統(tǒng))在文件頭部增加了幾個(gè)字節(jié)以表示編碼方式,三個(gè)字節(jié)(0xef, 0xbb, 0xbf)表示utf8扛门;兩個(gè)字節(jié)(0xff, 0xfe或者0xfe, 0xff)表示unicode鸠信;無(wú)表示gbk。

值得注意的是论寨,由于BOM不表意星立,在解析文件內(nèi)容的時(shí)候應(yīng)該舍棄,不然會(huì)造成解析出來(lái)的內(nèi)容頭部有多余的內(nèi)容葬凳。

unicode

unicode由于設(shè)計(jì)之初的種種外因绰垂、內(nèi)因,應(yīng)用不廣火焰,我也了解不多劲装,就簡(jiǎn)單說(shuō)明下:

  • utf系列是unicode的實(shí)現(xiàn)
  • 設(shè)計(jì)強(qiáng)制使用兩個(gè)字節(jié)表示所有字符,在英文場(chǎng)景下造成極大的浪費(fèi)昌简。相對(duì)的占业,utf-8以一個(gè)字節(jié)表示英文
  • 上小節(jié)提到有兩種方式表示unicode,分別是LE和BE纯赎。這個(gè)表示字節(jié)序谦疾,分別表示字節(jié)是從低位/高位開(kāi)始(因?yàn)槊總€(gè)字符都用到2個(gè)字節(jié),而且相反的順序能映射到不同的字符)犬金。node的Buffer API中基本都有相應(yīng)的2種函數(shù)來(lái)處理LE念恍、BE:
buf.readInt16LE(offset[, noAssert])
buf.readInt16BE(offset[, noAssert])

后端解碼

我第一次接觸到該類(lèi)問(wèn)題,使用的是node處理晚顷,當(dāng)時(shí)給我的選擇有node-iconv(系統(tǒng)iconv的封裝)以及iconv-lite(純js)峰伙。由于node-iconv涉及node-gyp的build,而開(kāi)發(fā)機(jī)是windows音同,node-gyp的環(huán)境準(zhǔn)備以及后續(xù)的一系列安裝和構(gòu)建词爬,讓我這樣的web開(kāi)發(fā)人員痛(瘋)不(狂)欲(吐)生(嘈),最后自然而然的選擇了iconv-lite权均。

解碼的處理大致示意如下:

const fs = require('fs')
const iconv = require('iconv-lite')

const buf = fs.readFileSync('/path/to/file')

// 可以先截取前幾個(gè)字節(jié)來(lái)判斷是否存在BOM
buf.slice(0, 3).equals(Buffer.from([0xef, 0xbb, 0xbf])) // utf8
buf.slice(0, 2).equals(Buffer.from([0xff, 0xfe])) // unicode

const str = iconv.decode(buf, 'gbk')

// 解碼正確的判斷需要根據(jù)業(yè)務(wù)場(chǎng)景調(diào)整
// 此處截取前幾個(gè)字符判斷是否有中文存在來(lái)確定是否解碼正確
// 也可以反向判斷是否有亂碼存在來(lái)確定是否解碼正確
// 正則表達(dá)式內(nèi)常見(jiàn)的\u**就是unicode編碼
/[\u4e00-\u9fa5]/.test(str.slice(0, 3))

前端解碼

隨著ES20151的瀏覽器實(shí)現(xiàn)越來(lái)越普及顿膨,前端編解碼也成為了可能锅锨。以前通過(guò)form表單上傳文件至后端解析的流程現(xiàn)在基本可以完全由前端處理,既少了與后端的網(wǎng)絡(luò)交互恋沃,而且因?yàn)橛薪缑姹馗悖脩?hù)體驗(yàn)上更直觀。

一般場(chǎng)景如下:

const file = document.querySelector('.input-file').files[0]
const reader = new FileReader()

reader.onload = () => {
    const content = reader.result
}
reader.onprogerss = evt => {
    // 讀取進(jìn)度
}
reader.readAsText(file, 'utf-8') // encoding可修改

支持的encoding列表2囊咏。這里有一個(gè)比較有趣的現(xiàn)象恕洲,如果文件包含BOM,比如聲明是utf-8編碼梅割,那指定的encoding會(huì)無(wú)效霜第,而且在輸出的內(nèi)容會(huì)去掉BOM部分,使用起來(lái)更方便户辞。

如果對(duì)編碼有更高要求的控制需求泌类,可以轉(zhuǎn)為輸出TypedArray:

reader.onload = () => {
    const buf = new Uint8Array(reader.result)
    // 進(jìn)行更細(xì)粒度的操作
}
reader.readAsArrayBuffer(file)

獲取文本內(nèi)容的數(shù)據(jù)緩沖以后,可以調(diào)用TextDecoder繼續(xù)解碼底燎,不過(guò)需要注意的是獲得的TypedArray是包含BOM的:

const decoder = new TextDecoder('gbk') 
const content = decoder.decode(buf)

如果文件比較大刃榨,可以使用Blob的slice來(lái)進(jìn)行切割:

const file = document.querySelector('.input-file').files[0]
const blob = file.slice(0, 1024)

文件的換行不同操作系統(tǒng)不一致,如果需要逐行解析双仍,需要視場(chǎng)景而定:

  • Linux: \n
  • Windows: \r\n
  • Mac OS: \r

注意:這個(gè)是各系統(tǒng)默認(rèn)文本編輯器的規(guī)則枢希,如果是使用其他軟件,比如常用的sublime朱沃、vscode苞轿、excel等等,都是可以自行設(shè)置換行符的逗物,一般是\n或者\(yùn)r\n呕屎。

前端編碼

可以使用TextEncoder將字符串內(nèi)容轉(zhuǎn)換成TypedBuffer:

const encoder = new TextEncoder() 
encoder.encode(String)

值得注意的是,從Chrome 53開(kāi)始敬察,encoder只支持utf-8編碼3秀睛,官方理由是其他編碼用的太少了。這里有個(gè)polyfill庫(kù)莲祸,補(bǔ)充了移除的編碼格式蹂安。

前端生成文件

掌握了前端編碼,一般都會(huì)順勢(shì)實(shí)現(xiàn)文件生成:

const a = document.createElement('a')
const buf = new TextEncoder()
const blob = new Blob([buf.encode('我是文本')], {
    type: 'text/plain'
})
a.download = 'file'
a.href = URL.createObjectURL(blob)
a.click()
// 主動(dòng)調(diào)用釋放內(nèi)存
URL.revokeObjectURL(blob)

這樣就會(huì)生成一個(gè)文件名為file.txt锐帜,后綴由type決定田盈。使用場(chǎng)景一般會(huì)包含導(dǎo)出csv,那只需要修改對(duì)應(yīng)的MIME type:

const blob = new Blob([buf.encode('第一行,1\r\n第二行,2')], {
    type: 'text/csv'
})

一般csv都是由excel打開(kāi)的缴阎,這時(shí)候發(fā)現(xiàn)第一列的內(nèi)容都是亂碼允瞧,因?yàn)閑xcel沿用了windows判斷編碼的邏輯,當(dāng)發(fā)現(xiàn)無(wú)BOM時(shí),采用gb18030編碼進(jìn)行解碼而導(dǎo)致內(nèi)容亂碼述暂,這時(shí)候只需要加上BOM即可:

const blob = new Blob([new Uint8Array([0xef, 0xbb, 0xbf]), buf.encode('第一行,1\r\n第二行,2')], {
    type: 'text/csv'
})

// or

const blob = new Blob([buf.encode('\ufeff第一行,1\r\n第二行,2')], {
    type: 'text/csv'
})

這里針對(duì)第二種寫(xiě)法稍微說(shuō)明下痹升,上文說(shuō)過(guò)utf-8編碼是unicode編碼的實(shí)現(xiàn),所以通過(guò)一定的規(guī)則畦韭,unicode編碼都可以轉(zhuǎn)為utf-8編碼疼蛾。而表明unicode的BOM轉(zhuǎn)成utf-8編碼其實(shí)就是表明utf-8的BOM。




附:

  1. TypedArray
  2. supported encodings
  3. TextEncoder
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末艺配,一起剝皮案震驚了整個(gè)濱河市察郁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌转唉,老刑警劉巖凡简,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件控妻,死亡現(xiàn)場(chǎng)離奇詭異猴贰,居然都是意外死亡丧叽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)期虾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人驯嘱,你說(shuō)我怎么就攤上這事镶苞。” “怎么了鞠评?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵茂蚓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我剃幌,道長(zhǎng)聋涨,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任负乡,我火速辦了婚禮牍白,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抖棘。我一直安慰自己茂腥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布切省。 她就那樣靜靜地躺著最岗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪朝捆。 梳的紋絲不亂的頭發(fā)上般渡,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼驯用。 笑死脸秽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晨汹。 我是一名探鬼主播豹储,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼淘这!你這毒婦竟也來(lái)了剥扣?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤铝穷,失蹤者是張志新(化名)和其女友劉穎钠怯,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體曙聂,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晦炊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宁脊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片断国。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖榆苞,靈堂內(nèi)的尸體忽然破棺而出稳衬,到底是詐尸還是另有隱情,我是刑警寧澤坐漏,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布薄疚,位于F島的核電站,受9級(jí)特大地震影響赊琳,放射性物質(zhì)發(fā)生泄漏街夭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一躏筏、第九天 我趴在偏房一處隱蔽的房頂上張望板丽。 院中可真熱鬧,春花似錦趁尼、人聲如沸檐什。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乃正。三九已至,卻和暖如春婶博,著一層夾襖步出監(jiān)牢的瞬間瓮具,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留名党,地道東北人叹阔。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像传睹,于是被迫代替她去往敵國(guó)和親耳幢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 字符集和編碼簡(jiǎn)介 在編程中常撑菲。可以見(jiàn)到各種字符集和編碼睛藻,包括ASCII,MBCS,Unicode等字符集。確切的說(shuō)...
    蘭山小亭閱讀 8,487評(píng)論 0 13
  • 可以看我的博客 lmwen.top 或者訂閱我的公眾號(hào) 簡(jiǎn)介有稍微接觸python的人就會(huì)知道邢隧,python中...
    ayuLiao閱讀 3,113評(píng)論 1 5
  • 最近校招季店印,特把自己面試中遇到的問(wèn)題整理整理,以鞏固自己的知識(shí)倒慧。 Java中對(duì)于容器有兩大類(lèi)存儲(chǔ)方式按摘,一種是單元素...
    末日沒(méi)有進(jìn)行曲閱讀 1,140評(píng)論 0 12
  • Write a program that can translate Morse code in the form...
    lintong閱讀 368評(píng)論 0 6
  • 詞:董書(shū)利 愛(ài)是你恨也是你 總有熟悉會(huì)牢記 一段刻骨經(jīng)歷 和一首對(duì)應(yīng)的歌曲 我是我你是你 路過(guò)熟悉一切就會(huì)想起 一...
    星巢文化閱讀 218評(píng)論 0 1