web知識高階 字元編譯碼

2021-09-11 09:29:15 字數 4861 閱讀 3362

作者簡介:nekron 螞蟻金服·資料體驗技術團隊

因為中文的博大精深,以及早期檔案編碼的不統一,造成了現在可能碰到的檔案編碼有gb2312gbkgb18030utf-8big5等。因為編譯碼的知識比較底層和冷門,一直以來我對這幾個編碼的認知也很膚淺,很多時候也會疑惑編碼名到底是大寫還是小寫,英文和數字之間是不是需要加「-」,規則到底是誰定的等等。

我膚淺的認知如下:

編碼說明

gb2312

最早的簡體中文編碼,還有海外版的hz-gb-2312

big5

正體中文編碼,主要用於台灣地區。些正體中文遊戲亂碼,其實都是因為big5編碼和gb2312編碼的錯誤使用導致

gbk簡體+繁體,我就當它是gb2312+big5,非國家標準,只是中文環境內基本都遵守。後來了解到,k居然是「擴充套件」的拼音首字母,這很中國。。。

gb18030

gb家族的新版,向下相容,最新國家標準,現在中文軟體都理應支援的編碼格式,檔案解碼的新選擇

utf-8

不解釋了,國際化編碼標準,html現在最標準的編碼格式。

首先要消化整個字元編譯碼知識,先要明確兩個概念——字符集和字元編碼。

顧名思義就是字元的集合,不同的字符集最直觀的區別就是字元數量不相同,常見的字符集有ascii字符集、gb2312字符集、big5字符集、 gb18030字符集、unicode字符集等。

字元編碼決定了字符集到實際二進位制位元組的對映方式,每一種字元編碼都有自己的設計規則,例如是固定位元組數還是可變長度,此處不一一展開。

常提到的gb2312、big5、utf-8等,如果未特殊說明,一般語義上指的是字元編碼而不是字符集。

字符集和字元編碼是一對多的關係,同一字符集可以存在多個字元編碼,典型代表是unicode字符集下有utf-8、utf-16等等。

當使用windows記事本儲存檔案的時候,編碼方式可以選擇ansi(通過locale判斷,簡體中文系統下是gb家族)、unicode、utf-8等。

為了清晰概念,需要指出此處的unicode,編碼方式其實是utf-16le。

有這麼多編碼方式,那檔案開啟的時候,windows系統是如何判斷該使用哪種編碼方式呢?

答案是:windows(例如:簡體中文系統)在檔案頭部增加了幾個位元組以表示編碼方式,三個位元組(0xef, 0xbb, 0xbf)表示utf-8;兩個位元組(0xff, 0xfe或者0xfe, 0xff)表示utf-16(unicode);無表示gb**。

值得注意的是,由於bom不表意,在解析檔案內容的時候應該捨棄,不然會造成解析出來的內容頭部有多餘的內容。

我們常接觸到的cpu都是le,所以windows裡unicode未指明位元組序時預設指的是le。

node的buffer api中基本都有相應的2種函式來處理le、be,貼個文件如下:

const buf = buffer.from([0, 5]);

// prints: 5

console.log(buf.readint16be());

// prints: 1280

console.log(buf.readint16le());

複製**

我第一次接觸到該類問題,使用的是node處理,當時給我的選擇有:

由於node-iconv涉及node-gyp的build,而開發機是windows,node-gyp的環境準備以及後續的一系列安裝和構建,讓我這樣的web開發人員痛(瘋)不(狂)欲(吐)生(嘈),最後自然而然的選擇了iconv-lite。

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

const fs = require('fs')

const iconv = require('iconv-lite')

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

// 可以先擷取前幾個位元組來判斷是否存在bom

buf.slice(0, 3).equals(buffer.from([0xef, 0xbb, 0xbf])) // utf-8

buf.slice(0, 2).equals(buffer.from([0xff, 0xfe])) // utf-16le

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

// 解碼正確的判斷需要根據業務場景調整

// 此處擷取前幾個字元判斷是否有中文存在來確定是否解碼正確

// 也可以反向判斷是否有亂碼存在來確定是否解碼正確

// 正規表示式內常見的\u**就是unicode碼點

// 該區間是常見字元,如果有特定場景可以根據實際情況擴大碼點區間

/[\u4e00-\u9fa5]/.test(str.slice(0, 3))

複製**

隨著es20151的瀏覽器實現越來越普及,前端編譯碼也成為了可能。以前通過form表單上傳檔案至後端解析內容的流程現在基本可以完全由前端處理,既少了與後端的網路互動,而且因為有介面反饋,使用者體驗上更直觀。

一般場景如下:

const file = document.queryselector('.input-file').files[0]

const reader = new filereader()

reader.onload = () =>

reader.onprogerss = evt =>

reader.readastext(file, 'utf-8') // encoding可修改

複製**

filereader支援的encoding列表,可查閱此處。

這裡有乙個比較有趣的現象,如果檔案包含bom,比如宣告是utf-8編碼,那指定的encoding會無效,而且在輸出的內容中會去掉bom部分,使用起來更方便。

如果對編碼有更高要求的控制需求,可以轉為輸出typedarray:

reader.onload = () => 

reader.readasarraybuffer(file)

複製**

獲取文字內容的資料緩衝以後,可以呼叫textdecoder繼續解碼,不過需要注意的是獲得的typedarray是包含bom的:

const decoder = new textdecoder('gbk') 

const content = decoder.decode(buf)

複製**

如果檔案比較大,可以使用blob的slice來進行切割:

const file = document.queryselector('.input-file').files[0]

const blob = file.slice(0, 1024)

複製**

檔案的換行不同作業系統不一致,如果需要逐行解析,需要視場景而定:

可以使用textencoder將字串內容轉換成typedbuffer:

const encoder = new textencoder() 

encoder.encode(string)

複製**

值得注意的是,從chrome 53開始,encoder只支援utf-8編碼2,官方理由是其他編碼用的太少了。這裡有個polyfill庫,補充了移除的編碼格式。

前端編碼完成後,一般都會順勢實現檔案生成,示例**如下:

const a = document.createelement('a')

const buf = new textencoder()

const blob = new blob([buf.encode('我是文字')], )

a.download = 'file'

a.href = url.createobjecturl(blob)

a.click()

// 主動呼叫釋放記憶體

url.revokeobjecturl(blob)

複製**

這樣就會生成乙個檔名為file的檔案,字尾由type決定。如果需要匯出csv,那只需要修改對應的mime type:

const blob = new blob([buf.encode('第一行,1\r\n第二行,2')], )

複製**

一般csv都預設是由excel開啟的,這時候會發現第一列的內容都是亂碼,因為excel沿用了windows判斷編碼的邏輯(上文提到),當發現無bom時,採用gb18030編碼進行解碼而導致內容亂碼。

這時候只需要加上bom指明編碼格式即可:

const blob = new blob([new uint8array([0xef, 0xbb, 0xbf]), buf.encode('第一行,1\r\n第二行,2')], )

// or

const blob = new blob([buf.encode('\ufeff第一行,1\r\n第二行,2')], )

複製**

這裡稍微說明下,因為utf-8和utf-16le都屬於unicode字符集,只是實現不同。所以通過一定的規則,兩種編碼可以相互轉換,而表明utf-16le的bom轉成utf-8編碼其實就是表明utf-8的bom。附:

typedarray

textencoder

本文介紹了字元編譯碼,感興趣的同學可以關注專欄或者傳送簡歷至'tao.qit####alibaba-inc.com'.replace('####', '@'),歡迎有志之士加入~

Web編譯碼方式集合

做中文站點,在url中使用中文引數太正常了,比如 http website list.asp keyword 關鍵字 在理想狀態下,是不需要做任何的處理,list.aspx頁面可以接收到 關鍵字 這個中文引數,但相當一部分情況下,會得到亂碼,所以為了安全保險起見,我們還是要對中文字元作一下編碼,編碼...

Nodejs高階 服務端字元編譯碼 亂碼處理

在web服務端開發中,字元的編譯碼幾乎每天都要打交道。編譯碼一旦處理不當,就會出現令人頭疼的亂碼問題。不少從事node服務端開發的同學,由於對字元編碼碼相關知識了解不足,遇到問題時,經常會一籌莫展,花大量的時間在排查 解決問題。文字先對字元編譯碼的基礎知識進行簡單介紹,然後舉例說明如何在node中進...

Nodejs高階 服務端字元編譯碼 亂碼處理

在web服務端開發中,字元的編譯碼幾乎每天都要打交道。編譯碼一旦處理不當,就會出現令人頭疼的亂碼問題。不少從事node服務端開發的同學,由於對字元編碼碼相關知識了解不足,遇到問題時,經常會一籌莫展,花大量的時間在排查 解決問題。文字先對字元編譯碼的基礎知識進行簡單介紹,然後舉例說明如何在node中進...