天天看點

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

目錄

一、介紹

二、簡略曆程

三、Unicode

3.1、一些概念

3.2、Unicode 編碼模型

3.3、編碼方式

四、其他

4.1、全角和半角

4.2、使用

4.3、編碼猜測

4.4、此字元非彼字元

參考

以下不一定正确,為我的個人了解。

一、介紹

計算機中資訊的傳遞、顯示都需要字元的存在,字元需要被編碼才能存入計算機。是以出現了字元編碼,在unicode出現之前,都可以用字元集和編碼方式來描述字元編碼。所謂字元集就是字元的集合;編碼方式就是如何将字元存入計算機的方式。但是unicode出現之後,字元集和編碼方式不能很好的概括字元編碼了,于是出現了更具抽象的術語來描述,如code point、code unit、character repertoire、coded character set、character encoding scheme等等等等。當然,這些概念也是能夠描述所有的編碼方式的。

下面會粗略(你沒看錯),,介紹,不過先了解下字元的大緻發展。

二、簡略曆程

最先出現的ASCII碼,一共128個字元,隻用到了7bit來存儲一個字元。但是适合英文字元的國家使用(美國、英國等),其他國家必須擴充ascii碼。一般計算機都是以8bit作為一個位元組,是以可以将最高位利用起來,那麼還能加入128個字元。于是出現了ASCII的各種擴充集,如iso-8859-1(Latin 1)、Windows-1252 (經常被誤作iso-8859-1)等等。

然而在我們東方國家,一個位元組不足以編碼所有的字元,更何況僅剩的一位bit最多隻能編碼128個字元。是以國人就想出了多位位元組編碼一個字元的辦法。于是出現了很多中國标碼,國内常使用的是GB18030吧?它相容遺留的編碼:GB2312、CP936和GBK1.0。GB18030定義了一個位元組(ASCII)、兩個位元組(擴充GBK)、四個位元組(UTF)的編碼方式,也就是說一個字元可能是由一個、兩個、或四個位元組編碼的。是以GB18030相容ASCII碼,而且比較容易映射到Unicode上。具體編碼方式可參考:Mapping。

上面的編碼都是從ASCII擴充而來的,都相容ASCII碼,于是這些編碼都被編入了ANSI标準中。然後windows将這些編碼收錄到了code page(這是編碼集吧?。。)中,不同國家通過指定不同代碼頁來使用不同的字元編碼,比如指定代碼頁54936使用GB18030字元編碼、指定代碼頁936使用GB2312編碼等等。不過這些複雜的内容了解一些就行了,畢竟現在windows都不用code page了,已經開始使用utf-16了,具體參考:History 。

大家都聽過ANSI吧?還經常混淆概念!!準确的說ANSI字元集是一個沒有模糊的、沒有準确定義的概念。有時候指的是code pages,有時候指的是windows-1252、ASCII、iso-8859,總之亂七八糟的!!具體參考:ANSI character set。是以看到有人說什麼ANSI編碼時,别理他!!或者根據語義猜測他的意思。

随着網絡發達和網際網路的普及,這麼多字元編碼,每個國家使用的都不相同,那麼在資訊交流上會出現極大的不友善。于是出現了Unicode(粗略來說,目前指的是字元集,對應多種編碼方式)啦,這裡隻粗略介紹下曆史過程,Unicode抽象的一些概念下一節提及。說到Unicode,那麼也必須提及UCS(Universal Coded Character Set)。

在緻力将全球編碼融入一個編碼系統時,兩個組織做着相同的工作:IEEE和Unicode Consortium。于是他們溝通,同步了他們對字元的指派,以至于兩者的字元集可以相容。兩個組織最初的想法是通過一個字元兩個位元組(最多65536個)來取代一個字元一個位元組(最多256個)的編碼方式。是以最初這種兩個位元組的編碼方式被稱為Unicode(這時,Unicode指的是字元集加編碼方式)。當初這種叫法到現在已經不合适了,因為随着字元集的增加,已經超過了

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

個了,IEEE介紹了另一種編碼方案UCS-4,一個字元用4個位元組來表示。是以之前字元編碼被稱作UCS-2。而Unicode Consortium則是修改它,使用code unit的概念,也就是一個字元可以使用一個或兩個16bit的code unit來表示,即2個或4個位元組來表示字元,于是能夠編碼所有的Unicode字元集。現在被叫作UTF-16,與之前的UCS-2區分開來,目前UCS-2的字元集隻是Unicode字元集的一個面(plane),貌似unicode共17個面吧。

但是呢~在西方國家中,它們本來是單位元組編碼的國家,現在使用utf-16導緻使用多一倍的存儲空間來存儲字元,導緻它們接收不了。于是出現了UTF-8來編碼Unicode字元集,相容ascii。

至于UTF-32,貌似沒啥大事件,它的曆史就不去了解了。

這裡就已經大緻介紹了字元編碼史,,我也不知道說的與實際是否有不一緻的地方,是以僅供參考。。。

三、Unicode

很多字元編碼都是從ASCII發展過來的,都相容ASCII,是以對這些編碼的概念描述都很簡單。即都是由字元集和編碼方式,而且字元集的編碼方式很簡單。但是Unicode發展到現在就真的複雜了,已經不能簡單的用字元集和編碼方式來描述它了(盡管上一小結就是這樣來了解它的,但也隻是為了簡化描述而已)。

3.1、一些概念

以下概念來自于:Character encoding,但為我的了解,不曉滴對不對。

  • a character:語義文本中最小的單元,這裡指的是字元,如“a”,“1”,“中”。
  • a character set:字元的集合。集合有大有小,不如ASCII的字元集隻有128個,unicode很多個,目前最新版本超過136000個。
  • a coded character set:一個字元集,但是裡面的每個字元都配置設定了一個唯一的數字與之對應。
  • a code point:就是a coded character set中的一個值,它對應一個字元,比如一個code point為U+0041,對應字元“A”。
  • a code unit:就是一個bit序列,用來編碼字元的。可以了解為編碼code point的最小單元,就是說一個code point可能由多個code unit來編碼。對于Unicode中不同的編碼方式,code unit的位數不同,比如utf-8中8位code unit,utf-16中16位,utf-32中32位。

Unicode中使用U+****(十六進制)的方式來表示code point,而且code point的範圍在U+0000到U+10FFFF之間,一個17個面。第0個面範圍在U+0000到U+FFFF之間,稱為Basic Multilingual Plane (BMP),其他16個plane(U+010000到U+10FFFF)被稱為supplementary plane。

下面給出了一張表格,展現了字元、code point、code unit之間的關系,當然code unit還不是計算機中具體的存儲形式,因為還要考慮到大小端和其他的問題。

character a b c U+10400
code point U+0061 U+0062 U+0063 U+10400
code unit in UTF-32 00000061 00000062 00000063 00010400
code unit in UTF-16 0061 0062 0063 d800,dc00
code unit in UTF-8 61 62 63 90,90,80

3.2、Unicode 編碼模型

unicode編碼模型認為一個字元編碼由下面這個部分組成:

  1. character repertoire:一個系統所支援的全部的抽象的字元集。
  2. code character set(CCS):一個映射character到code point的函數。
  3. character encoding form(CEF):一個code point到code unit的映射。也就是将數字(code point)轉化為固定長度的bit序列,比如8位、16位、32位的unit,正如上述表格展示的一樣。
  4. character encoding scheme(CES):一個映射,将code unit映射到基于8位bit存儲的檔案系統中或基于8位bit儲存的網絡中。要考慮大小端、對于每個unit如何壓縮所需位元組數量的問題。

上面的第一個部分就是character repertoire(抽象的字元集),#1和#2構成coded character set。在Unicode出現之前,一些簡單的單位元組編碼可以看做是将上面#3和#4部分去掉了、簡化了,即code point=code unit=byte。比如ASCI碼中的“a”,code point為U+61,8位的code unit 也為61,而byte為‭00111101‬(單位元組不用考慮大小端問題)。

3.3、編碼方式

對于Unicode來說,有很多中編碼方式:UTF-8,UTF-16BE,UTF-16LE,UTF-16,UTF-32,UTF-32LE,UTF-32BE等。Unicode應該指的是coded character set(編碼字元集),為這些編碼所共同的部分,即有共同的編碼字元集。而這些字元集的差別在于#3和#4部分的不同。注意,這裡的UTF-**,後面的數字指的是code unit的位數。

UTF-8的中的字元需要1,2,3或4個位元組(1到4個code unit)來編碼一個字元,适合作為網絡上、磁盤中資訊的編碼方式。code point在U+0000到U+007F之間編碼方式與ASCII相同,是以相容ASCII。

UTF-16中的字元需要2或4個位元組(1或2個code unit)來編碼一個字元。看到了吧,utf-16不是必定為2位元組哦,因為unicode的字元個數超過了65536個。如果超過65536,需要2個code unit,被稱為surrogate pair。在上述步驟#4中,将code unit轉化為byte,需要考慮大小端的問題。注意,utf-16的code unit為16位。如一個code unit為 55 66,那麼該和編碼呢?比如大端編碼:55 66,小端編碼:66 55。都是可行的!是以出現了不同儲存形式,為了區分他們,有人想出了一個辦法,就是添加BOM,一個位元組的大小的标記,位于檔案最前端。當解析該檔案時,通過讀取該Bom,就能知道該如何解析檔案。但是如果沒有Bom呢?畢竟不是硬性要求。 RFC 2781中有規定,預設使用大端存放,但是windows反其道而行,,,使用了小端存放,,,那麼使用預設存放方式就不靠譜了。但是所幸的是,可以通過檢查null byte來探測出。作為比較,舉個例子,如果我連檔案的編碼方式都不知道呢?會怎麼樣?現在探測不出來了,隻能靠經驗猜,是的,有很多算法可以經過統計檔案資料來猜測編碼方式。還有一種方法就是通過指定編碼方式來顯式給出:UTF-16BE或UTF-16LE。現在可以知道UTF-16、UTF-16BE和UTF-16LE之間的差別了吧,這對于UTF-32也是一樣。

UTF-32采用固定4位元組編碼字元,不像UTF-8和UTF-16那樣的可變長度的編碼方式。也存在大小端問題,請參考UTF-32。

注意哦,UTF-16和UTF-32不相容ASCII,因為它們表示字母的位數比較多,盡管code point數值和ASCII的數值相同。

四、其他

這裡講講其他相關的内容。

4.1、全角和半角

含有同樣語義的字母在不同國家有不同展現或者叫形狀,那麼它們的code point到底相同還是不相同呢?這裡我給不出答案,當我知道在咋們國家,對于标點符号來說,它們的code point就是不同的。這就涉及到了全角和半角的概念了。全角的标點符号占兩個位元組,而半角一個位元組,也就是西方國家使用的标點符号。這也就造成我寫代碼時天天要切換中英文輸入法了。。煩不勝煩!

4.2、使用

說那麼多編碼的事沒啥子用,要了解它用在哪裡才是最重要的。utf-8不用說,由于它的可變長度,且常用字元位元組數都比較少,還能表示所有的Unicode的字元集,是以常用于網絡上傳輸資訊。在個人檔案中儲存資訊的編碼也大多是utf-8。而utf-32固定長度為4個位元組,由于浪費空間太大,不是很常用。

這裡着重要講的是UTF-16,因為code point在U+0000到U+FFFF之間的字元都用2個位元組編碼,而U+010000到U+10FFFF之間的字元很少用,是以便于計算機管理。而且UTF-16使用的是Unicode字元集,便于國際化,是以UTF-16用途很廣泛,很适合在記憶體中編碼。是以如今的windows系統都使用了UTF-16編碼方式(之前使用code page、USC-2啥滴)。但是unix/linux 或macOS使用的是UTF-8.

java中原先使用的是UCS-2,之後使用了UTF-16(估計就如之前所說,Unicode字元集日益完善,超過了65536,UCS-2表示不了了,是以更換了)。是以呀~  java中的char不一定是表示一個字元了,而是可能兩個char表示一個字元哦~~。有可能,一些string的實作中,code unit才是傳回字元串長度、索引元素的依據,而不是code point了!具體java如何實作我不知道。

javascript使用USC-2或utf-16.

html中的實體(Entity)使用的是utf-16。

其他的參考标題連結。

4.3、編碼猜測

如果不給出編碼或不知道編碼,一般系統會猜測編碼,通過一定的算法來猜測。是以,有時候沒有猜對就會亂碼。寫程式時就不用猜測了,因為程式員自己知道編碼,可以采用正确的編碼來解碼檔案。但是你作為一個程式員,連自己檔案的編碼都不知道,,,那活該亂碼。。

比如網頁,浏覽器獲得網頁編碼的方式有兩種,一種是從響應頭部中context-type字段來獲得檔案編碼;一種是從html檔案中的<meta/>标簽中獲得檔案編碼。有人會問,都不知道檔案編碼,你怎麼解析檔案獲得meta中繼資料的?這是因為很多編碼都相容ASCII碼,而且meta元素盡量寫在前面沒有就沒有其他編碼的幹擾,能夠順利提取出meta中的編碼資訊。因為兩個資訊都是可選提供的,如果都沒有那麼浏覽器隻能根據一定算法猜測了,猜錯了就亂碼。

又比如windows系統,盡管它現在系統記憶體中使用utf-16存儲字元,但是使用notepad編輯文本時還是預設ANSI。我在第二節中提到過ANSI,這裡指的code page,不同區域window預設的代碼頁不同,是以使用的字元編碼不同。這裡,我國使用的是GB系統,可能是GB2312吧,不曉滴。預設儲存後,當打開這個檔案時,notepad這個軟體就會猜測了,因為它不知道編碼格式。猜錯了就亂碼。下面示範一遍猜錯的例子:

打開notepad,輸入“聯通”:

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

然後儲存,它預設使用ANSI編碼(看到ANSI我就呵呵):

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

這裡先暫停,我先噴一噴windows。看到了吧,這裡就是造成我們最大誤解的地方,編碼windows自己都沒有分歧,就瞎搞。是以一般寫代碼的人都不用notepad,而是用notepad++或sublime之類的專業一點的編輯器。

然後打開文本,發現亂碼了。

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

這是因為資訊太少,被notepad誤認為是utf-8了,這是怎麼看出的呢?你現在點選另存為,就可以看出,它把檔案的編碼誤認為utf-8了。

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

現在我們删掉之前的文本,重新寫一個,這時在“聯通”後面多補幾個字,這樣notepad就有了更多的資訊來猜測檔案編碼了,正确率大大提高:

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

看,猜測正确了,這也是一個為什麼“聯通幹不過移動”笑話的來源。【手動微笑】。

4.4、此字元非彼字元

雖然說unicode字元集中收集的都是字元,但是有些字元在使用者看來是不完整的。比如說下面兩個字元(U+0061和U+0300):

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

此時使用者認識a,但是不認識第二個,第二個其實是第四聲音标(accent)。。。但是使用者不認為音标也屬于字元咋辦?如果這樣呢?

字元編碼一、介紹二、簡略曆程三、Unicode四、其他參考

編碼為(U+0061U+0300),這是使用者就認識了,這是個帶音标的a。

是以說,在使用者的角度來看,一個字元有時是由多個code point組成的。

參考

主要參考的一篇文章,盡管是03年寫的,說明别人是真的牛。。:https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/

主要參考:https://en.wikipedia.org/wiki/Character_encoding

What's the difference between encoding and charset?

What is the character encoding of String in Java?

UTF-16:https://en.wikipedia.org/wiki/UTF-16

UTF-8:https://en.wikipedia.org/wiki/UTF-8#Comparison_with_single-byte_encodings

Comparison of Unicode encodings:https://en.wikipedia.org/wiki/Comparison_of_Unicode_encodings

UCS:https://en.wikipedia.org/wiki/Universal_Coded_Character_Set

Unicode:https://en.wikipedia.org/wiki/Unicode

GB18030:https://en.wikipedia.org/wiki/GB_18030

Windows code page:https://en.wikipedia.org/wiki/Windows_code_page

ANSI character set:https://en.wikipedia.org/wiki/ANSI_character_set

ASCII:https://en.wikipedia.org/wiki/ASCII

聯通的例子來自于此,但是文章的源頭找不到了:https://blog.csdn.net/jdbdh/article/details/81143599

浏覽器context-type的例子:https://stackoverflow.com/questions/43148464/how-do-browsers-determine-the-encoding-used

JavaScript’s internal character encoding: UCS-2 or UTF-16?:https://mathiasbynens.be/notes/javascript-encoding

html entities:https://www.w3schools.com/html/html_entities.asp

utf-8轉換工具:http://www.ltg.ed.ac.uk/~richard/utf-8.cgi

繼續閱讀