關於字符編碼你應該知道的事情

讀完本文你將瞭解的知識點

  1. 爲何 Windows 上使用 Notepad 會出現亂碼
  2. 爲何 Emoji 表情在有些手機上顯示不許確
  3. 爲何 Emoji 在沒有作過特殊優化的數據庫中存儲失敗
  4. 爲何使用 Linux 開發的代碼他人使用 Windows 開發後換行符全變了
  5. 爲何在 JS 中 [...'👨‍👨‍👦‍👦'] => ["👨", "‍", "👨", "‍", "👦", "‍", "👦"]
  6. 新版本 ECMAScript 針對 JavaScript 編碼問題作了哪些改進
  7. 爲何使用 Google Chrome 打開 JS 文件,文件中的中文字符會變成亂碼

比特、字節

  1. 比特 ( Bit / Binary digit ) 縮寫爲 b,計算機最小的存儲單位,以 0/1 來表示值html

  2. 字節 ( Byte ) 縮寫爲 B,8 個比特表示一個字節git

    在計算機內部,全部的信息最終都表示爲一個二進制的序列。每個二進制位 ( Bit ) 有 0 和 1 兩種狀態,所以八個二進制位就能夠組合出 256 種狀態,這被稱爲一個字節 ( Byte ) ,也就是說,一個字節一共能夠用來表示 256 種不一樣的狀態或者符號。若是咱們製做一張對應表格,對於每個 8 位二進制序列,都對應惟一的一個符號。每個狀態對應一個符號,就是 256 個符號,從 0000 0000 到 1111 1111 。正則表達式

ASCII 與 EASCII

  1. ASCII (American Standard Code for Information Interchange,美國信息交換標準代碼)算法

    1967 年發佈,最後更新於 1986 年,共定義了 128 ( 2⁷ ) 個字符( 0x00 - 0x7F ) ,其中 33 個字符爲不可打印字符 ( 0x00 - 0x1F & 0x7F ),95 個可打印字符 ( 0x20 - 0x7E )數據庫

    可打印字符爲標準鍵盤中可輸入的字符,以下所示:瀏覽器

    10 個數字 ( 0-9 ),26×2 個大小寫字母 ( a-z A-Z ) ,32 個標點符號 1 個空格 ( ,./;'[]\-=~!@#$%^&*()_+{}|:"<>? )服務器

    ASCII 的侷限在於只能顯示 26 個基本拉丁字母、阿拉伯數目字和英式標點符號,所以只能用於顯示現代美國英語,而其餘攜帶相似於重音符號的字母沒法顯示 ( naïve、café )工具

  2. EASCII ( Extended ASCII,延伸美國標準信息交換碼 )優化

    因爲 ASCII 的自然不足,它的變種體迅速出現,兼容字符集對ASCII的處理編碼

  • ISO/IEC 646 1972年

    該標準來自數個國家標準,最主要的是美國的 ASCII 標準,ISO 646 爲了表示歐洲各類語言的帶附加符號( diacritical mark )的變音字母,因爲沒有碼位空間去直接編碼這些變音字母,因此用幾個標點符號來兼做變音字母的附加符號

  • ISO/IEC 8859

    擴展字符:0xA0 ( 160 ) - 0xFF ( 255 ) 淘汰了 ISO 646 編碼標準

    ISO 8859 統一了此前各國各語言的單獨編碼的混亂局面;廢棄了 ISO 646 使用的退格鍵開始的轉義序列來表示變音字母的方法,而是在 G1 區域直接編碼表示變音字母。

    ISO 8859 有 15 個子版本( 1-11,13-16 ),其中囊括了大部分歐州語言,英語由於沒有重音字母,全部可使用其中任何一個子版本表示

    Microsoft Codepage 1252 爲 ISO 8859-1 的超集,擴充了 0x80 - 0x9F 來編碼一些可打印字符 ( € ‚ ƒ „ … † ‡ ˆ ‰ Š ‹ Œ Ž ‘ ’ 「 」 • – — ˜ ™ š › œ ž Ÿ )

    例如在中國 GB/T 1988-80 標準中: $ u+0024 替換爲 ¥ u+00A5 ,~ u+007E 替換爲 u+203E

  1. ANSI

    Windows 操做系統上的 ANSI 編碼並非指的是美國國家標準學會 ( ANSI ),而是用來指稱多個不一樣的代碼頁,好比在簡體中文編碼操做系統中,ANSI 實際使用 GB 系字符編碼

中文

  • GB2312 ( 1981 ) 6763 個漢字,最第一版本,雙字節編碼
  • GB12345 ( 1993 ) 6866 個漢字,爲了適應繁體漢字信息處理而制定的標準
  • GBK ( 1995 ) 21886個漢字和圖形符號,不屬於國家標準
  • GB18030 ( 2000 ),70244 個字符,基於 GBK,現行版本

國際通用標準

  • Unicode ( 萬國碼、國際碼、統一碼、單一碼 )

    最第一版本:1.0.0 發佈,1991 年 10 月發佈,7161 個字符 當前正式版本 Unicode 11.0 ( 2018 年 6 月 ) 擁有 137374 個字符 當前最新版本:Emoji 12.0 Beta

    表示方法:

    • 基本平面:一般會用 "U+" 而後緊接着 4 個 16 進制的數字來表示這一個字,可表示 6 萬餘個字符
    • 其餘平面使用 "U-" 而後接着 8 個 16 進制數字表示
  • ISO/IEC 10646 ( UCS / 通用字符集 )

    該字符集包括了其餘全部字符集,保證了與其餘字符集的雙向兼容,ISO 10646 有三種實現級別,不一樣的實現級別能支持的字符數量不一樣

    與 Unicode 的關係:

  • 全部字符在相同位置且有相同名字
  • Unicode 標準裏有詳細說明某些語言和文字的表達算法等
  • ISO 承諾,ISO 10646 將不會替超出 U+10FFFF( Unicode 編碼以 U+ 開頭) 的 UCS-4 編碼賦值

UTF ( Unicode Transformation Format )

Unicode 是一個字符集,其實現方式稱爲 Unicode 轉換格式,即 UTF

  • UTF-32

    Unicode 與 UCS 合併以前已經產生了 UCS-4 編碼方式,UCS-4 使用了 32 位來表示每一個編碼,爲了兼容 Unicode 產生了 UTF-32 標準,編碼空間限制在了 0x000000 - 0x10FFFF 之間,所以能夠說 UTF-32 是 UCS-4 的子集。因爲 UTF-32 的編碼空間佔用過大,所以在 HTML5 標準中明確規定不能使用 UTF-32 進行編碼

  • UTF-16

    UTF-16 編碼擁有定長和變長兩個編碼特色,對於 Unicode 基本平面的字符,UTF-16 佔用兩個字節,對於輔助平面的字符,UTF-16 編碼佔用四個字節

    Unicode 規範定義,每個文件的最前面分別加入一個表示編碼順序的字符,這個字符的名字叫作 「零寬度非換行空格 ( zero width no-break space )」,用 FE FF 表示。但在不一樣計算機系統中對字節順序的理解是不一致的,即出現了大端序 ( UTF-16 BE ) 與小端序 ( UTF-16 LE ) 兩種狀況。文本頭部使用 FE FF 與 FF FE 進行區分,此區分符稱爲「字節順序標記 ( BOM ) 」

    如何肯定雙字節和四字節:

    在基本平面內,從 U+D800 到 U+DFFF 是一個空段,不對應任何碼點,這個空段用來映射輔助平面的字符,即一個輔助平面的字符,被拆成兩個基本平面的字符表示。

    例如: 👨 能夠表示爲 U+D83D U+DC68

  • UTF-8

    因爲前兩種編碼方式的編碼規則對與英語國家來講很是浪費(2-4 字節編碼)

    UTF-8 當前使用 1-6 個字節爲每一個字符編碼

  1. 對於單字節的符號,字節的第一位設爲 0 ,後面 7 位爲這個符號的 Unicode 碼。所以對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。

  2. 對於n字節的符號( n > 1 ),第一個字節的前n位都設爲 1,第 n + 1 位設爲0,後面字節的前兩位一概設爲 10。剩下的沒有說起的二進制位,所有爲這個符號的 Unicode 碼。

  3. 帶有附加符號的拉丁文、希臘文、西裏爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文及它拿字母則須要兩個字節編碼 ( Unicode 範圍由 U+0080 至 U+07FF )。

  4. 其餘基本多文種平面 ( BMP ) 中的字符(這包含了大部分經常使用字,如大部分的漢字)使用三個字節編碼 ( Unicode範圍由U+0800至U+FFFF )。

  5. 其餘極少使用的 Unicode 輔助平面的字符使用 4-6 字節編碼

  • UCS-2

    JavaScript 採用了 Unicode 字符集。可是隻支持一種編碼方式。JS 最早採用的編碼既不是 UTF-16 也不是 UTF-32 或 UTF-8 ,而是 UCS-2 。UTF-16 明確宣佈是 UCS-2 的超集。UTF-16 中基本平面字符延用 UCS-2 編碼。輔助平面字符定義了 4 個字節的表示方法。

    JS 只能處理 UCS-2 編碼,形成全部字符在這門語言中都是兩個字節,若是是四個字節的字符。會被當作兩個雙字節的字符處理。

    二者的關係簡單說,就是 UTF-16 取代了 UCS-2,或者說 UCS-2 整合進了 UTF-16。因此,如今只有 UTF-16,沒有 UCS-2。

碼點和平面

字符會從 0 開始爲每一個字符指定一個編碼, 這個編碼叫作碼點

舉例 Unicode 中給字符進行分區定義,每一個區稱爲一個面,Unicode 擁有 0-16 共 17 個平面,每一個平面 16⁴ 個字符

平面 字符值 描述
0號平面 U+0000 - U+FFFF 基本多文種平面
1號平面 U+10000 - U+1FFFF 多文種補充平面
2號平面 U+20000 - U+2FFFF 表意文字補充平面
3號平面 U+30000 - U+3FFFF 表意文字第三平面(未正式使用)
4 - 13號平面 U+40000 - U+DFFFF (還沒有使用)
14號平面 U+E0000 - U+EFFFF 特別用途補充平面
15號平面 U+F0000 - U+FFFFF 保留做爲私人使用區(A區)
16號平面 U+100000 - U+10FFFF 保留做爲私人使用區(B區)

題首問題

  • 爲何 Windows 上使用 Notepad 會出現亂碼

    Windows 上的 Notepad 軟件在保存文件時默認使用的是 ANSI 編碼保存,而在打開的時候須要猜想 txt 文件的編碼方式,若是文檔中出現了 ANSI 編碼之外的字符,則在打開時候可能會出現編碼識別錯誤的狀況,因爲 txt 文件爲純文本文件,沒有保存文檔編碼信息的區域,則此問題可能一直存在。

    解決該問題可在保存文件的時候使用 UTF-8 編碼保存,但須要注意的是:

    Windows 的 Notepad 應用使用 UTF-8 保存的時候實際使用的爲 UTF-8 BOM 方式,其表現爲在文本最開頭添加 EF BB BF ,這部分稱爲 UTF-8 字節順序標記 ,該方式並不是強制標準,若是在代碼文件中使用該方式保存則有可能出現運行錯誤。

  • 爲何 Emoji 表情在有些手機上顯示不許確

    當前 iOS 12 使用的 Unicode 版本爲 11,而大衆使用比較的 Android 8.0 使用的Unicode 版本爲 9,若是在 Android 系統中出現了新版本的字符,則會出現沒法顯示或顯示錯誤的狀況。

    例如在 Unicode 8.0 中加入了 5 個菲茨帕特里克修飾符,用來調節人形表情的膚色,若是在低於此版本的 Unicode 中顯示的字符爲兩個字符,分別是顏色加人偶。

    另外 Unicode 新版本中使用 U+200D 零寬連字 ( ZWJ ) 將多個 Emoji 連起來,例如 👨‍👨‍👦‍👦 => 👨👨👦👦

  • 爲何 Emoji 在沒有作過特殊優化的數據庫中存儲失敗

    Emoji 表情佔用 4 個字節,可是 MySQL 數據庫使用的 utf-8 默認編碼最多隻能存儲 3 個字節 ( UTF-8 標準支持最長編碼爲 6 字節 ),就會致使存儲不進去,在讀取的時候讀取不完整,致使亂碼

    修復方法爲:修改數據庫字符集爲 uft8mb4,若是數據庫鏈接池中對字符集做出了設置須要在連接中去掉 characterEncoding 參數

  • 爲何使用 Linux 開發的代碼他人使用 Windows 開發後換行符全變了

    Windows 系列系統使用的換行標誌爲 CRLF,該換行標誌與 Unix/Linux 的 LF 換行及 macOS 的 CR 換行不相同。 若是在代碼工程中使用了 Code Lint 工具自動格式化,可能會使代碼中的 LF 換行自動轉換爲 CRLF 換行,Git 中也能捕獲或忽略這個變化。 另外,從 Windows 10 1803 開始,支持 Unix/Linux 的 LF 換行及 macOS 的 CR 換行。

  • 爲何在 JS 中 [...'👨‍👨‍👦‍👦'] => ["👨", "‍", "👨", "‍", "👦", "‍", "👦"]

    👨‍👨‍👦‍👦 是 2015 年添加到 Emoji 2.0 中的新字符,使用 U+200D 零寬連字 (ZWJ) 將4個 Emoji 連起來,可以使用如下代碼檢測

    [...'👨‍👨‍👦‍👦'].forEach(e=>{console.log(e.codePointAt().toString(16))})

  • 新版本 ECMAScript 針對 JavaScript 編碼問題作了哪些改進

    因爲 JavaScript 使用的是隻支持雙字節編碼的 USC-2 編碼方式,因此全部超過二字節編碼的 Unicode 字符都沒法在 JavaScript 中處理

    例如 '👨'.charCodeAt().toString(16) 輸出的結果爲 d83d ,而👨的Unicode 碼點卻不是 d83d,形成這樣的緣由爲 JavaScript 只處理了該字符的前兩個字節

    爲了解決這些問題,ECMAScript 6 種加強了對新版本 Unicode 的支持。 例如:

    1. for of 循環中對雙字節以上字符能識別正確長度
    2. Array.from 等方法能正確劃分字符串
    3. 支持直接使用碼點表示字符,例如'\ud83d\udc68' === '👨' === '\u{1F468}'
    4. String.fromCodePoint()String.prototype.codePointAt() 等方法代替 String.fromCharCode()String.prototype.charCodeAt() 等方法,以用於支持 UTF-16 編碼字符
    5. 正則表達式提供了 u 修飾符,對正則表達式添加4字節碼點的支持
    6. 提供了normalize方法,容許"Unicode正規化" ,例如:'\u01D1'.normalize() === '\u004F\u030C'.normalize()
  • 爲何使用 Google Chrome 打開 JS 文件,文件中的中文字符會變成亂碼

    因爲 2017 年更新的某版本 Chrome 中,去除了對 JS 文件默認編碼 UTF-8 的支持,使用了系統默認編碼(例如中文操做系統使用 GB18030 )對 JS 文件的解碼,因此致使 JS 文件中的中文字符變成亂碼。

    解決方法有兩種:

    1. 在文件服務器中對返回頭的 Content-Type 設置加上 charset=UTF-8
    2. 瀏覽器中使用插件改變網頁編碼方式,例如使用 FEHelper 工具

參考資料:

本文首發地址

blog.shoyuf.top

第二次在掘金上發文章,歡迎各位評論區中吐槽指正

相關文章
相關標籤/搜索