讀完本文你將瞭解的知識點
- 為什麼 Windows 上使用 Notepad 會出現亂碼
- 為什麼 Emoji 表情在有些手機上顯示不準確
- 為什麼 Emoji 在沒有做過特殊優化的資料庫中儲存失敗
- 為什麼使用 Linux 開發的程式碼他人使用 Windows 開發後換行符全變了
- 為什麼在 JS 中
[...'????'] => ["?", "", "?", "", "?", "", "?"]
- 新版本 ECMAScript 針對 JavaScript 編碼問題做了哪些改進
- 為什麼使用 Google Chrome 開啟 JS 檔案,檔案中的中文字元會變成亂碼
位元、位元組
-
位元 ( Bit / Binary digit ) 縮寫為
b
,計算機最小的儲存單位,以 0/1 來表示值 -
位元組 ( Byte ) 縮寫為
B
,8 個位元表示一個位元組在計算機內部,所有的資訊最終都表示為一個二進位制的序列。每一個二進位制位 ( Bit ) 有 0 和 1 兩種狀態,因此八個二進位制位就可以組合出 256 種狀態,這被稱為一個位元組 ( Byte ) ,也就是說,一個位元組一共可以用來表示 256 種不同的狀態或者符號。如果我們製作一張對應表格,對於每一個 8 位二進位制序列,都對應唯一的一個符號。每一個狀態對應一個符號,就是 256 個符號,從 0000 0000 到 1111 1111 。
ASCII 與 EASCII
-
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é )
-
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
-
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 個位元組為每個字元編碼
-
對於單位元組的符號,位元組的第一位設為 0 ,後面 7 位為這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。
-
對於n位元組的符號( n > 1 ),第一個位元組的前n位都設為 1,第 n + 1 位設為0,後面位元組的前兩位一律設為 10。剩下的沒有提及的二進位制位,全部為這個符號的 Unicode 碼。
-
帶有附加符號的拉丁文、希臘文、西裡爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文及它拿字母則需要兩個位元組編碼 ( Unicode 範圍由 U+0080 至 U+07FF )。
-
其他基本多文種平面 ( BMP ) 中的字元(這包含了大部分常用字,如大部分的漢字)使用三個位元組編碼 ( Unicode範圍由U+0800至U+FFFF )。
-
其他極少使用的 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 的支援。 例如:
- for of 迴圈中對雙位元組以上字元能識別正確長度
- Array.from 等方法能正確劃分字串
- 支援直接使用碼點表示字元,例如
'\ud83d\udc68' === '?' === '\u{1F468}'
String.fromCodePoint()
和String.prototype.codePointAt()
等方法代替String.fromCharCode()
和String.prototype.charCodeAt()
等方法,以用於支援 UTF-16 編碼字元- 正規表示式提供了 u 修飾符,對正規表示式新增4位元組碼點的支援
- 提供了normalize方法,允許"Unicode正規化" ,例如:
'\u01D1'.normalize() === '\u004F\u030C'.normalize()
-
為什麼使用 Google Chrome 開啟 JS 檔案,檔案中的中文字元會變成亂碼
由於 2017 年更新的某版本 Chrome 中,去除了對 JS 檔案預設編碼 UTF-8 的支援,使用了系統預設編碼(例如中文作業系統使用 GB18030 )對 JS 檔案的解碼,所以導致 JS 檔案中的中文字元變成亂碼。
解決方法有兩種:
- 在檔案伺服器中對返回頭的 Content-Type 設定加上 charset=UTF-8
- 瀏覽器中使用外掛改變網頁編碼方式,例如使用 FEHelper 工具
參考資料:
本文首發地址
第二次在掘金上發文章,歡迎各位評論區中吐槽指正