前端編解碼

背景

因?yàn)橹形牡牟┐缶?,以及早期文件編碼的不統(tǒng)一,造成了現(xiàn)在可能碰到的文件編碼有gb2312gbk、gb18030、utf-8、big5等。因?yàn)榫幗獯a的知識比較底層和冷門,一直以來我對這幾個編碼的認(rèn)知也很膚淺,很多時(shí)候也會疑惑編碼名到底是大寫還是小寫,英文和數(shù)字之間是不是需要加“-”,規(guī)則到底是windows定的還是國家定的等等。

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

編碼 說明
gb2312 最早的簡體中文編碼,還有海外版的hz-gb-2312
big5 繁體中文編碼,主要用于臺灣地區(qū)。小時(shí)候有些繁體中文游戲亂碼,都是因?yàn)閎ig5編碼和gb2312編碼的識別混亂導(dǎo)致
gbk 簡體+繁體,我就當(dāng)它是gb2312+big5,向下兼容,在解碼時(shí)我一般選擇該編碼,因?yàn)榇虻淖稚?。后來了解到,這個就是windows幫中國“好心的”擴(kuò)展了中文編碼,致使編碼庫又多了個新成員
gb18030 gb家族的新版,向下兼容,國家標(biāo)準(zhǔn),現(xiàn)在中文軟件都理應(yīng)支持的編碼格式,文件解碼的新選擇
utf-8 不解釋了,國際化編碼標(biāo)準(zhǔn),html現(xiàn)在最標(biāo)準(zhǔn)的編碼格式。注:windows上的文本編輯器用到的utf-8是帶BOM的

BOM

當(dāng)使用windows記事本保存文件的時(shí)候,編碼方式可以選擇ANSI(通過locale判斷,簡體中文系統(tǒng)下是gb家族)、Unicode、UTF-8等。那文件打開的時(shí)候,系統(tǒng)是如何判斷該使用哪種編碼方式呢?

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

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

unicode

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

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

后端解碼

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

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

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

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

// 可以先截取前幾個字節(jié)來判斷是否存在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ù)場景調(diào)整
// 此處截取前幾個字符判斷是否有中文存在來確定是否解碼正確
// 也可以反向判斷是否有亂碼存在來確定是否解碼正確
// 正則表達(dá)式內(nèi)常見的\u**就是unicode編碼
/[\u4e00-\u9fa5]/.test(str.slice(0, 3))

前端解碼

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

一般場景如下:

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。這里有一個比較有趣的現(xiàn)象,如果文件包含BOM,比如聲明是utf-8編碼,那指定的encoding會無效,而且在輸出的內(nèi)容會去掉BOM部分,使用起來更方便。

如果對編碼有更高要求的控制需求,可以轉(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ù)解碼,不過需要注意的是獲得的TypedArray是包含BOM的:

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

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

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

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

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

注意:這個是各系統(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開始,encoder只支持utf-8編碼3,官方理由是其他編碼用的太少了。這里有個polyfill庫,補(bǔ)充了移除的編碼格式。

前端生成文件

掌握了前端編碼,一般都會順勢實(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()
// 主動調(diào)用釋放內(nèi)存
URL.revokeObjectURL(blob)

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

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

一般csv都是由excel打開的,這時(shí)候發(fā)現(xiàn)第一列的內(nèi)容都是亂碼,因?yàn)閑xcel沿用了windows判斷編碼的邏輯,當(dāng)發(fā)現(xiàn)無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'
})

這里針對第二種寫法稍微說明下,上文說過utf-8編碼是unicode編碼的實(shí)現(xiàn),所以通過一定的規(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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 字符集和編碼簡介 在編程中常??梢砸姷礁鞣N字符集和編碼,包括ASCII,MBCS,Unicode等字符集。確切的說...
    蘭山小亭閱讀 9,079評論 0 13
  • 可以看我的博客 lmwen.top 或者訂閱我的公眾號 簡介有稍微接觸python的人就會知道,python中...
    ayuLiao閱讀 3,388評論 1 5
  • 最近校招季,特把自己面試中遇到的問題整理整理,以鞏固自己的知識。 Java中對于容器有兩大類存儲方式,一種是單元素...
    末日沒有進(jìn)行曲閱讀 1,169評論 0 12
  • Write a program that can translate Morse code in the form...
    lintong閱讀 417評論 0 6
  • 詞:董書利 愛是你恨也是你 總有熟悉會牢記 一段刻骨經(jīng)歷 和一首對應(yīng)的歌曲 我是我你是你 路過熟悉一切就會想起 一...
    星巢文化閱讀 260評論 0 1

友情鏈接更多精彩內(nèi)容