徹底搞懂字符編碼

2022-01-10 前端晚間課

收錄於話題 #unicode 1個

背景

在日常開發中很少接觸到字符的概念,大部分語言對字符的轉換都已經封裝的足夠好,不需要開發人員過多考慮編碼解碼的問題。但是字符編碼又經常在開發中遇見,所以這一篇文章就是解決,到底什麼是字符集以及其編碼。舉個很簡單的例子,調用字符串的length函數,其中的英文,漢字,emoji,長度分別是多少?length長度是如何計算的?iOS中的NSString,Javascript中的String在內存中的編碼方式是什麼?搞懂了字符編碼,這些問題就迎刃而解了。

編碼、ASCII

我們知道計算機只處理0和1,所有可見的文件,視頻,音頻等都是以二進位的形式存儲和運算的。我們用八個二進位位表示一個字節,那麼一個字節就可以代表256種「字符」。舉個例子,字母「A」定義為65,用二進位表示是0100 0001。

這種把A轉換成0100 0001的形式就是一次映射的過程。再來看ASCII碼一共有128個字符,那麼就有128個映射。一個字節就足足的可以表示了。ASCII就是最早期的字符集。

字符集

隨著計算機的普及,需要做映射的字符越來越多,光常用漢字就幾千個了,這時候ASCII碼已經不夠用了,湧現了很多字符集,ISO-8859,GB2312,GBK等,直到後來為了解決各個字符集各自為戰的問題,分別產生了Unicode 組織和 ISO-10646工作小組,最後這兩家組織也合併了,形成現如今的Unicode.

Unicode

Unicode是一種計算行業標準,用於對世界上大多數書寫系統中表示的文本進行一致的編碼,表示和處理。該標準由Unicode聯盟維護,截至2019年5月,最新版本Unicode 12.1包含137994個字符的庫,涵蓋150個現代和歷史腳本以及多個符號集和表情符號。Unicode標準的字符庫與ISO / IEC 10646同步,並且兩者的代碼相同。Unicode也是一種字符集。通常會用U+十六進位表示,可存儲0000 ~ 10FFFF 共 1114112 個值,2^16(65536)個號碼組成一個平面,一共有17個平面,其中第一個0號平面佔了絕大部分常用的字符。看下圖可以比較直觀的了解,其中每個最小的格子是一個字節即代表256個編碼點,每個大格子有65536個編碼點,藍色區域是已被使用的區域,綠色是自用區,紅色區域是代理區。

再將前三個格子放大,藍綠色部分是漢字,棕色部分是朝鮮語,由這兩張圖可以最直觀的了解Unicode存儲空間。Unicode的實現方式有多種,其中最常用的是UTF-8和UTF-16,下面逐一介紹兩個實現原理。

UTF-8

UTF-8是目前使用最廣的Unicode編碼方式,它是一種可變長的編碼方式,從1個字節到4個字節不等。下圖是它的編碼規則:

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

2.對於n字節的符號(n > 1),第一個字節的前n位都設為1,第n + 1位設為0,後面字節的前兩位一律設為10。剩下的沒有提及的二進位位,全部為這個符號的 Unicode 碼。

根據上表,解讀 UTF-8 編碼非常簡單。如果一個字節的第一位是0,則這個字節單獨就是一個字符;如果第一位是1,則連續有多少個1,就表示當前字符佔用多少個字節。

下面,還是以漢字「嚴」為例,演示如何實現 UTF-8 編碼。

「嚴」的 Unicode 是4E25(100111000100101),根據上表,可以發現4E25處在第三行的範圍內(0000 0800 - 0000 FFFF),因此「嚴」的 UTF-8 編碼需要三個字節,即格式是1110xxxx 10xxxxxx 10xxxxxx。然後,從「嚴」的最後一個二進位位開始,依次從後向前填入格式中的x,多出的位補0。這樣就得到了,「嚴」的 UTF-8 編碼是11100100 10111000 10100101,轉換成十六進位就是E4B8A5。

優點:

1.兼容ASCII.

2.沒有字節序問題。(後面會講到字節序)

3.對於英文編碼較短,佔用空間小。

4.可變長,空間足夠大

5.容錯性好,中間丟失字節,後面的字節還是可以根據編碼規則解碼,不影響後面的字符生成。

缺點:

1.對於中日韓的語言,一個字符需要三個字節表示,佔用空間大。

2.計算長度效率低,由於是變長的,所以在計算字符串長度的時候執行效率比較低。

UTF-16

UTF-16也是經常用到的編碼方式,同樣它也是可變長的編碼方式,下圖是編碼規則,字符長度2個字節或者4個字節表示一個字符。

隨即就有了一個問題,當我們遇到兩個字節,怎麼看出它本身是一個字符,還是需要跟其他兩個字節放在一起解讀形成一個字符?

U+D800到U+DFFF是一個空段,這些碼點不對應任何字符,編號大於U+0FFFF的字符,一半在U+D800到U+DBFF之間,一半在U+DC00到U+DFFF之間,當我們遇到兩個字節,發現它的碼點在U+D800到U+DBFF之間,就可以斷定,緊跟在後面的兩個字節的碼點,應該在U+DC00到U+DFFF之間,這四個字節必須放在一起形成一個字節。剛好可以涵蓋輔助面的字符。

優點:

1.由於是固定2個字節和4個字節,所以在計算字符串長度、執行索引操作時速度很快。

缺點:

1.UTF-16 能表示的字符數有 6 萬多,但是實際上目前 Unicode 5.0 收錄的字符已經達到 99024 個字符,早已超過 UTF-16 的存儲範圍。

2.UTF-16 存在字節序問題(大端和小端)使用時需要提前協定好。

3.容錯性差,因為存在字節序的問題,如果中間某一個字節丟失時可能會導致後面的解碼錯誤。

UTF-32

4 個字節表示一個代碼值,固定長度,多出來的部分前面補0,這種編碼方式佔空間較多,使用場景很少。

字節序

字節序是指數據在存儲器中的存放順序,分為大端和小端兩種。之所以會存在字節序的問題是因為寄存器的長度要大於一個字節,不同的作業系統讀取字節的順序不一樣,

大端模式,是指數據的高字節在前,保存在內存的低地址中,與人類的讀寫法一致,數據的低字節在後,保存在內存的高地址中,文件前綴FE FF。(Mac OS是大端模式)

小端模式,是指數據的高字節在後,保存在內存的高地址中,而數據的低字節在前,保存在內存的低地址中,文件前綴FF FE。(x86和一般的OS(如windows,FreeBSD,Linux)使用的是小端模式)。

所以UTF-16存在一個字節序的問題,需要在文件前面聲明,而UTF-8不存在這個問題,原因在於UTF-8的最小編碼單位是1個字節,不會存在兩個字節誰在高位誰在地位的問題。

還是以漢字「嚴」為例,Unicode 碼是4E25,需要用兩個字節存儲,一個字節是4E,另一個字節是25。存儲的時候,4E在前,25在後,這就是 Big endian 方式(4E 25)。25在前,4E在後,這是 Little endian 方式(25 4E)。

用途

UTF-8,廣泛用於數據存儲及傳輸:例如html文檔中的<meta charset="UTF-8">,以及python文件當出現中文的時候會在頂部加上「# coding: UTF-8」等。

UTF-16,而一些流行語言比如Java、Javascript、Python、Objective-C等字符串內部字符串都用UTF-16編碼.在計算字符串長度搜索是的效率較好。

Objective-C中的NSString

Java中的String類

實踐

1.日常字母,漢字,表情分別用UTF-8和UTF-16表示分別用多少字節?

字母UTF-8用一個字節,UTF-16用兩個字節。

大部分的漢字UTF-8編碼後由三個字節如下圖的「嚴」是e4b8a5,而用UTF-16編碼僅用2個字節即4e25。

大部分表情等特殊符號UTF-8編碼後佔4個字節,UTF-16編碼後也佔4個字節。

2.字符計算長度length方法是怎麼計算的?

像NSString,java,javascript等語言,由於是UTF-16編碼的,在計算長度的時候由總字節/2得來的。

3.實際開發時,如果想計算字符實際長度該怎麼計算?

1)一種是通過判斷碼點所在位置進行判斷,只要落在0xD800到0xDBFF的區間,就要連同後面2個字節一起讀取.

var index = -1;var string = '𠁒12';var length = string.length;var output = [];while (++index < length) {  var charCode = string.charCodeAt(index);  var character = string.charAt(index);  if (charCode >= 55296 && charCode <= 56319) {    output.push(character + string.charAt(++index));  } else {    output.push(character);  }}console.log(output) consolo.log(0xD800 ===55296) 

2.)ECMAScript 6版本 增強了對Unicode的支持,基本解決了這個問題。

let s = '𠁒12';let output = [];for(let s of string ){     output.push(s)}console.log(output) 

Array.from(string).length

4.javascript字符串和碼點之間的轉換方法?

console.log(String.fromCodePoint(9731, 9733, 9842, 0x2F804));

var icons = '☃★♲';console.log(icons.codePointAt(1));

5.文件編碼是UTF-8,和字符串UTF-16編碼之間有什麼關係?

這裡以python為例,我們都知道,磁碟上的文件都是以二進位格式存放的,其中文本文件都是以某種特定編碼的字節形式存放的。對於程序原始碼文件的字符編碼是由編輯器指定的,比如我們使用Pycharm來編寫Python程序時會指定工程編碼和文件編碼為UTF-8,那麼Python代碼被保存到磁碟時就會被轉換為UTF-8編碼對應的字節(encode過程)後寫入磁碟。當執行Python代碼文件中的代碼時,Python解釋器在讀取Python代碼文件中的字節串之後,需要將其轉換為Unicode字符串(decode過程)之後才執行後續操作。

總結

上面講了Unicode字符集的起源,它的三種編碼方式,UTF-8,UTF-16,UTF-32,編碼空間,編碼規則,優缺點以及用途,之後結合實際場景應用介紹了js中一些實踐方法,不同語言會有自己的方式,還有就是文件編碼和執行時字符編碼。這些知識點在實際開發中不常見,但是深入了解其原理,對日常開發和解決問題會有幫助。

http://reedbeta.com/blog/programmers-intro-to-unicode/https://zh.wikipedia.org/zh-cn/Unicode#%E6%A8%99%E6%BA%96http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.htmlhttp://www.ruanyifeng.com/blog/2014/12/unicode.htmlhttps://www.cnblogs.com/yyds/p/6171340.html

相關焦點

  • 建議收藏,徹底搞懂字符編碼問題,從此告別中文亂碼
    在沒有深入理解其原理之前,會覺得中文編碼問題比較謎,莫名其妙地亂碼,又稀裡糊塗地好了。字符編碼是計算機技術的基石,本文希望幫助大家徹底梳理清楚字符編碼問題,不僅知其然,還知其所以然,擺脫被中文亂碼支配的感覺。在講解中文編碼問題之前,我們需要先講講英語編碼,其解決方案是 ASCII。
  • 徹底搞懂python字符編碼,看這篇就夠了!
    不論你是有著多年經驗的 Python 老司機還是剛入門 Python 不久,你一定遇到過UnicodeEncodeError、UnicodeDecodeError 錯誤,每當遇到錯誤我們就拿著 encode、decode 函數翻來覆去的轉換,有時試著試著問題就解決了,有時候怎麼試都沒轍,只有借用 Google,百度各種 大神幫忙,但似乎很少去關心問題的本質是什麼,下次遇到類似的問題重蹈覆轍,那麼你有沒有想過一次性徹底把
  • 一文搞懂字符編碼問題,從此告別中文亂碼
    在沒有深入理解其原理之前,會覺得中文編碼問題比較謎,莫名其妙地亂碼,又稀裡糊塗地好了。字符編碼是計算機技術的基石,本文希望幫助大家徹底梳理清楚字符編碼問題,不僅知其然,還知其所以然,擺脫被中文亂碼支配的感覺。在講解中文編碼問題之前,我們需要先講講英語編碼,其解決方案是 ASCII。
  • 徹底搞懂這煩人的編碼與亂碼!
    ❝我們平時在處理文本文件或者網絡請求時,時不時會遇到亂碼的情況,這篇文章就帶你徹底搞懂編碼和亂碼❞首先,我們要知道,在計算機中,一切都是用0和1來表示的。普通的txt文件、或者客戶端發過來的數據等等,這些一切其實都是通過0和1轉化而來的。「那它是怎樣從0和1轉化我們人能看懂的字母或漢字呢?」
  • 一個逗逼程式設計師解說 字符編碼 問題
    ,規定:一個小於127的字符的意義與原來(就是ASCII)相同,但兩個大於127的字符連在一起時,就表示一個漢字,前面的一個字節(稱之為高字節)從0xA1用到 0xF7,後面一個字節(低字節)從0xA1到0xFE,這樣我們組合出大約7000多個簡體漢字,每個漢字佔用2個字節的腦容量,而這就是GB2312字符編碼;有了這套編碼,我們天朝(還有新加坡)就和這個小娃娃沒羞沒臊的過上了幸福的小日子
  • 計算機編碼|各類字符編碼
    也就是說當採用GB2312編碼時,如果某字節最高位為0,則表示這是一個單字節的ASCII字符,如果最高位為1,則表示這是一個雙字節的編碼字符。GBK並不是國家標準,所以後來國家又出手搞了GB18030,兼容GBK,但好像GB18030用得並不廣泛,所以我們這裡也不作介紹了。由於每個國家都各有一套編碼標準,因此就造成一個問題了,在兩種不同編碼標準下,同一份文本,在這個標準下顯示是正常的,在哪一個標準下就顯示亂碼了。
  • 字符編碼(Charset & Encoding)
    什麼是字符編碼?人類在與計算機交互時,用的都是人類能讀懂的字符,如中文字符、英文字符、日文字符等而計算機只能識別二進位數,詳解如下"#二進位數即由0和1組成的數字,例如010010101010。字符編碼中的編碼指的是翻譯或者轉換的意思,即將人能理解的字符翻譯成計算機能識別的數字字符編碼表的發展史 現代計算機起源於美國,所以最先考慮僅僅是讓計算機識別英文字符,於是誕生了ASCII表
  • 幫你徹底弄懂常見的中文字符編碼
    為了解決該問題,就有了這篇文章……在計算機眼裡讀到的所有文字都是由0和1組成的字符串,為了能讓漢字正常顯示在屏幕上,我們需要做以下兩件事情:【1】給所有的漢字一個獨一無二的數字編號,做一個數字編號到漢字的mapping關係(即字符集)【2】把這個數字編號能用0和1表示出來這裡需要說明的是,第【2】件事情並不是直接把數字編號用二進位表示出來那麼簡單,還要處理多個字連在一起的時候如何做分隔的問題。
  • 一個「字符編碼」問題,被他解說的這麼逗逼!
    ,規定:一個小於127的字符的意義與原來(就是ASCII)相同,但兩個大於127的字符連在一起時,就表示一個漢字,前面的一個字節(稱之為高字節)從0xA1用到 0xF7,後面一個字節(低字節)從0xA1到0xFE,這樣我們組合出大約7000多個簡體漢字,每個漢字佔用2個字節的腦容量,而這就是GB2312字符編碼;有了這套編碼,我們天朝(還有新加坡)就和這個小娃娃沒羞沒臊的過上了幸福的小日子
  • Python 2.x 字符編碼終極指南
    一文中對字符編碼進行了詳細的討論,並通過一些簡單的小程序驗證了我們對於字符編碼的認識。至於什麼是 ASCII,UTF-8等,在人機互動之字符編碼 中有詳細的說明,這裡不再贅述。下面結合具體的例子,來看看編碼、解碼的細節問題。python2.x 中的字符串在程序設計中,字符串一般是指一連串的字符,比如hello world!、你好或者もしもし(日語)等等。
  • 【C++】搞懂char與wchar_t字符串
    在windows下,char*的字符串編碼是多字節,用的本地編碼,就是我們的GBK。linux下char*直接就是utf8,所以兩個平臺char*字符串直接交流是不行的。。。1.2. 字符數組對於str5這種字符數組,因為末尾沒有0,所以把他當作字符串直接輸出就會有內存裡其他數據,就出現了「燙」。。
  • 字符集和字符編碼
    ASCII編碼:將ASCII字符集轉換為計算機可以接受的數字系統的數的規則。使用7位(bits)表示一個字符,共128字符;但是7位編碼的字符集只能支持128個字符,為了表示更多的歐洲常用字符對ASCII進行了擴展,ASCII擴展字符集使用8位(bits)表示一個字符,共256字符。ASCII字符集映射到數字編碼規則如下圖所示:
  • 字符編碼那些事兒
    在這些編碼裡,我們還把數學符號、羅馬希臘的字母、日文的假名們都編進去了,連在 ASCII 裡本來就有的數字、標點、字母都統統重新編了兩個字節長的編碼,這就是常說的全形字符,而原來在127號以下的那些就叫半角字符了。
  • ALEVEL CS ASCII and Unicode 字符編碼
    ( 會缺少很多常見字符 ) 一時間,各個國家都開始制定自己的文字標準,比較著名的有西歐的 ISO-8859 系列標準、微軟的 Windows-12xx 系列標準。 以及咱們中國的 GB 2312 文字編碼標準。
  • 談談字符編碼:Unicode編碼與emoji表情編碼
    介紹字符編碼前,先要明確概念:碼位(碼點),對應編碼術語中英文中的code point,指的是一個編碼標準中為某個字符設定的數值,具有唯一性與一一對應性。碼位只規定了一個字符對應的數值,並沒有規定這個數值如何存儲,視編碼方案不同有不同的存儲方式。
  • 圖解中文字符編碼-Go語言例解
    今天幾個同事在處理一個有關中文字符編碼的問題,感覺他們對字符編碼這件事依然理解不夠透徹。這裡用圖文方式對中文字符編碼做一個簡要的解釋,例子使用Go語言。我們知道每個英文字母和數字在計算機中都會對應一個字節,或者說用一個字節來表示,這就是最初的ASCII碼。
  • 一文詳解C++字符編碼的轉換
    【CPP開發者導讀】:在處理東方語言(中日韓)時,經常會遇到各種編碼問題,而且被這類問題搞的暈頭轉向。
  • 字符集和字符編碼(Charset & Encoding)
    它是現今最通用的單字節編碼系統(但是有被Unicode追上的跡象),並等同於國際標準ISO/IEC 646。ASCII字符集:主要包括控制字符(回車鍵、退格、換行鍵等);可顯示字符(英文大小寫字符、阿拉伯數字和西文符號)。ASCII編碼:將ASCII字符集轉換為計算機可以接受的數字系統的數的規則。
  • [亂碼必看]深度長文--聊聊Unicode字符編碼
    他們採用的方法很簡單:廢了所有的地區性編碼方案,重新搞一個包括了地球上所有文化、所有字母和符號的編碼!他們打算叫它」Universal Multiple-Octet Coded Character Set」,簡稱 UCS, 俗稱 「unicode「。unicode開始制訂時,計算機的存儲器容量極大地發展了,空間再也不成為問題了。
  • Luna Tech | Character Encoding(字符編碼)- 1
    Character Encoding(字符編碼)在計算機領域,Character Encoding[6](字符編碼)的目的是則把字符變成二進位的 01 進行傳輸。字符編碼常用術語character(字符):有語義的最小單位字符;character set(字符集):多個字符組成的集合,可能被多種語言使用,比如 Latin character set(拉丁字符集)被用於英文和大部分的歐洲語言;coded character set(簡稱 CCS,編碼字符集)