字符集与字符编码详解
字符
字符和字節不太一樣,任何一個文字或符號都是一個字符,但是所占字節不一定,不同的編碼導致一個字符所占的內存不同。
例如:標點符號"+"是一個字符,漢字"我們"是兩個字符,在GBK編碼中一個漢字占2個字節,在UTF-8編碼中一個漢字占3個字節。
隨著時代的發展,程序員們希望在計算機中顯示字符,但計算機只能識別0和1的二進制數。于是就有了編碼規范。
編碼規范
所謂的字符集其實是一套編碼規范中的子概念,為了顯示字符,國際組織就指定了編碼規范,希望使用不同的二進制數來表示代表不同的字符,這樣電腦就可以根據二進制數來顯示其對應的字符。我們通常就稱呼其為xx編碼,xx字符集。
例如:GBK 編碼規范,根據這套編碼規范,計算機就可以在中文字符和二進制數之間相互轉換。而使用GBK編碼就可以使計算機顯示中文字符。
編碼規范里的3個子概念:
1、字庫表
一套編碼規范不一定包含世界上所有的字符,每套編碼規范都有自己的使用場景。而字庫表就存儲了編碼規范中能顯示的所有字符,計算機就是根據二進制數從字庫中找到字符然后顯示給用戶,相當于一個存儲字符的數據庫。
例如:幾乎所有漢字都保存在GBK編碼規范的字庫表中。所以可以顯示漢字,但法語、俄語并不在其字庫表中,所以GBK不能顯示法語、俄語等不包含在其中的字符。
2、編碼字符集(字符集)
在一個字庫表中,每一個字符都有一個對應的二進制地址,而編碼字符集就是這些地址的集合。字符集和字庫表一一對應,相互轉換,這是電腦識別字符的關鍵。
如果把世界上不同國家文明的所有字符都放在一起組成一個集合,那么我們常見的 ASCII、GB2312、GBK、GB18030、BIG5 字符集都只是包含了該集合的一部分而已。而Unicode字符集是可以包含所有國家文明中的所有字符的。
3、字符編碼(編碼方式)
知道字庫表和編碼字符集后,我們就可以直接使用二進制地址來得到字符了。但直接使用字符對應的二進制地址來顯示文字是十分浪費的,Unicode 編碼規范中包括了幾百萬個字符,想要包括幾百萬個不同的字符,起碼需要3個字節的容量,為了方便將來擴展,Unicode還保留了更多未使用的空間,最多可以存儲4個字節的容量。
因此為了區分每個字符,哪怕是00000000 00000000 00000000 00001111這種其實只占了一個字節的字符,我們也要為它分配4個字節的空間,這就導致一個可以用1G保存的文件,現在需要4G才能保存,這是極其浪費的做法。
于是程序員就定下了一套算法來節省空間,而每種不同的算法都被稱作一種編碼方式。一套編碼規范可以有多種不同的編碼方式,不同的編碼方式有不同的適應場景。例如:UTF-8就是一種編碼方式,Unicode是一種編碼規范。此外Unicode還有UTF-16、UTF-32這兩種編碼方式。不同的編碼方式節約的空間不同。總結:一個較短的二進制數,通過一種編碼方式,轉換成編碼字符集中正常的地址,然后在字庫表中找到一個對應的字符,最終顯示給用戶。
ASCII編碼
計算機一開始發明的時候是用來解決數字計算的問題,后來人們發現,計算機還可以做更多的事,例如文本處理。但由于計算機只識"數",因此人們必須告訴計算機哪個數字來代表哪個特定字符,例如65代表字母'A',66代表字母'B',以此類推。但是計算機之間字符-數字的對應關系必須得一致,否則就會造成同一段數字在不同計算機上顯示出來的字符不一樣。因此美國國家標準協會ANSI制定了一個標準,規定了常用字符的集合以及每個字符對應的編號,這就是ASCII字符集(Character Set)也稱ASCII碼(American Standard Code for Information Interchange,美國信息交換標準代碼)。
ISO-8859-1編碼
為了表示更多的歐洲等國家使用的字符,對原始的ASCII編碼范圍進行了擴充,采用一個字節256種不同狀態來表示256種不同的字符。ISO-8859-1編碼是單字節編碼,向下兼容ASCII,它的編碼范圍使用了單字節內的所有空間(即8位,0-255),在支持ISO-8859-1的系統中傳輸和存儲其他任何編碼的字節流都不會被拋棄。換言之,把其他任何編碼的字節流當作ISO-8859-1編碼看待都沒有問題。這是個很重要的特性,MySQL數據庫默認編碼是Latin1就是利用了這個特性。ASCII編碼是一個7位的容器,ISO-8859-1編碼是一個8位的容器。
多字節字符集(MBCS)和中文字符集
上面我們提到的字符集都是基于單字節編碼,也就是說一個字節翻譯成一個字符,這對于拉丁語系國家來說足夠了。但是對于亞洲國家來說,256個字符是遠遠不夠用。因此在保持和ASCII字符集的兼容下,就發明了多字節編碼方式,相應的字符集就稱為多字節字符集。對于單字節字符集來說,代碼頁中只需要有一張碼表即可,上面記錄著256個數字代表的字符,程序只需要做簡單的查表操作就可以完成編解碼的過程。對于多字節字符集,代碼頁中通常會有很多碼表,根據第一個字節來選擇不同的碼表進行解析。
目前最常用的中文字符集GB2312,涵蓋了所有簡體字符以及一部分其他字符;GBK則在GB2312的基礎上加入了對繁體字符等其他非簡體字符。GBK字符集中所有字符占2個字節,不論中文英文都是2個字節。從ASCII、GB2312到GBK,這些編碼方法是向下兼容的,即同一個字符在這些方案中總是有相同的編碼,后面的標準支持更多的字符。在這些編碼中,英文和中文可以統一地處理。區分中文編碼的方法是高字節的最高位不為0。GB2312、GBK都屬于雙字節字符集(DBCS)
ANSI標準、國家標準、ISO標準
不同ASCII衍生字符集的出現,會讓文檔交流變得非常困難。如美國ANSI組織制定了ANSI標準字符編碼,ISO組織制定的各種ISO標準字符編碼,還有各國也會制定一些國家標準字符集,例如中國的GBK,GB2312和GB18030。
操作系統在發布的時候,通常會往機器里預裝這些標準的字符集還有平臺專用的字符集,這樣只要你的文檔是使用標準字符集編寫的,通用性就比較高了。例如你用GB2312字符集編寫的文檔,在中國大陸內的任何機器上都能正確顯示。同時,我們也可以在一臺機器上閱讀多個國家不同語言的文檔了,前提是本機必須安裝該文檔使用的字符集。
Unicode的出現
雖然通過使用不同字符集,我們可以在一臺機器上查閱不同語言的文檔,但是我們仍然無法解決一個問題:在一份文檔中顯示所有字符。為了解決這個問題,我們需要一個全人類達成共識的巨大的字符集,這就是Unicode字符集。Unicode簡稱為UCS。Unicode字符集涵蓋了目前人類使用的所有字符,并為每個字符進行統一編號,分配唯一的字符碼(Code Point)。Unicode字符集將所有字符按照使用上的頻繁度劃分為17個層面(Plane),每個層面上有2^16=65536個字符碼空間。其中第0個層面BMP,基本涵蓋了當今世界用到的所有字符。其他的層面要么是用來表示一些遠古時期的文字,要么是留作擴展。我們平常用到的Unicode字符,一般都是位于BMP層面上的。
在Unicode出現之前,所有的字符集都是和具體編碼方案綁定在一起的,都是直接將字符和最終字節流綁定死了。例如ASCII編碼系統規定使用7比特來編碼ASCII字符集;GB2312以及GBK字符集,限定了使用最多2個字節來編碼所有字符,并且規定了字節序。這樣的編碼系統通常用簡單的查表,也就是通過代碼頁就可以直接將字符映射為存儲設備上的字節流了。這種方式的缺點在于,字符和字節流之間耦合得太緊密了,從而限定了字符集的擴展能力。因此Unicode在設計上考慮到了這一點,將字符集和字符編碼方案分離開。
例如同樣是對Unicode字符"A"進行編碼,UTF-8字符編碼得到的字節流是0x41,而UTF-16(大端模式)得到的是0x00 0x41。UCS只是規定如何編碼,并沒有規定如何傳輸、保存這個編碼,關鍵在于通信雙方都要認可。UTF是"UCS Transformation Format"的縮寫。
UCS-2、UCS-4
UCS有兩種格式:UCS-2和UCS-4。UCS-2就是用兩個字節編碼,它有2^16=65536個碼位,UCS-4就是用4個字節編碼(實際上只用了31位,最高位必須為0),它有2^31=2147483648個碼位。對于Unicode來說,UCS-2和UCS-4是字符碼(內碼)。
UTF-8
UTF-8編碼方案采用1-4個字節來編碼字符,對于ASCII字符的編碼使用單字節,和ASCII編碼一摸一樣。對于其他字符,則使用2-4個字節來表示。其中首字節前置1的數目代表正確解析所需要的字節數,剩余字節的高2位始終是10。例如首字節是1110xxxx,前置有3個1說明正確解析總共需要3個字節,需要和后面2個以10開頭的字節結合才能正確解析得到字符。
如上表所示,對于只需要1個字節的字符,UTF-8采用ASCII碼的編碼方式,最高位補0來表示。例如:01000001我們就是用01000001來表示,對于一個字節的字符,其實就是直接使用地址表示。而對于n個字節的字符(n>1),即大于一個字節的字符,采用第一個字節前n位補1。第n+1位填0,后面字節的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符號的unicode字符碼。
例如:漢字嚴的Unicode碼是4E25轉換成二進制就是0100111000100101,根據上表可知使用UTF-8字符編碼后占3個字節,因此前3位是1,第4位(n+1位)是0,后面兩個字節中每個字節的前兩位都是10,即1110 xxxx 10 xxxxxx 10 xxxxxx。填充進去后就變成了1110 0100 10 11100010 100101共計24位占3個字節。
由此可見,英文在UTF-8字符編碼后只占1個字節,中文占了3個字節。
帶簽名的UTF-8
帶簽名指的是字節流以BOM標記開始。很多軟件會"智能"地探測當前字節流使用的字符編碼,這種探測過程出于效率考慮,通常會提取字節流前面若干個字節,看看是否符合某些常見字符編碼的編碼規則。由于UTF-8和ASCII編碼對于純英文的編碼是一樣的,無法區分開來,因此通過在字節流最前面添加BOM標記可以告訴軟件,當前使用的是Unicode編碼,判別成功率就十分準確了。但是需要注意,不是所有軟件或者程序都能正確處理BOM標記,例如PHP就不會檢測BOM標記,直接把它當普通字節流解析了。因此如果你的PHP文件是采用帶BOM標記的UTF-8進行編碼的,那么有可能會出現問題。
UTF-16
UTF-16 是介于 UTF-8 和 UTF-32 之間,使用2個或者4個字節來存儲的,其長度即固定又可變。
UTF-32
UTF-32 是固定長度的編碼,始終占用 4 個字節,足以容納所有的 Unicode 字符。所以直接存儲 Unicode 編號即可,不需要任何編碼轉換。浪費了空間,提高了效率。
GB18030
任何能夠將Unicode字符映射為字節流的編碼都屬于Unicode編碼。中國的GB18030編碼,覆蓋了Unicode所有的字符,因此也算是一種Unicode編碼。只不過他的編碼方式并不像UTF-8或者UTF-16一樣,將Unicode字符的編號通過一定的規則進行轉換,而只能通過查表的手段進行編碼。
GBK、GB2312等與UTF-8之間都必須通過Unicode編碼才能相互轉換:
GBK、GB2312 -- Unicode字符碼 -- UTF-8
UTF-8 -- Unicode字符碼 -- GBK、GB2312
字節序
計算機硬件有兩種儲存數據的方式:大端字節序(big endian)和小端字節序(little endian)
大端字節序:高位字節在前,低位字節在后,這是人類讀寫數值的方法。
小端字節序:低位字節在前,高位字節在后。
舉例來說:0x1234567的大端字節序和小端字節序的寫法如下
為什么會有小端字節序?計算機電路先處理低位字節,效率比較高,因為計算都是從低位開始的。所以計算機的內部處理都是小端字節序。但是人類還是習慣讀寫大端字節序,所以除了計算機的內部處理,其他的場合幾乎都是大端字節序,比如網絡傳輸和文件儲存。
計算機處理字節序的時候,不知道什么是高位字節,什么是低位字節。它只知道按順序讀取字節,先讀第一個字節,再讀第二個字節。如果是大端字節序,先讀到的就是高位字節,后讀到的就是低位字節。小端字節序正好相反。所以只有在讀取的時候,才必須區分字節序,其他情況都不需要考慮。處理器讀取外部數據的時候,必須知道數據的字節序,將其轉成正確的值。然后,就正常使用這個值,完全不用再考慮字節序。即使是向外部設備寫入數據,也不用考慮字節序,正常寫入一個值即可。外部設備會自己處理字節序的問題。
大小端
計算機界對于傳輸多字節字(由多個字節來共同表示一個數據類型)時,是先傳高位字節(大端)還是先傳低位字節(小端)也有著不一樣的看法。無論是寫文件還是網絡傳輸,實際上都是往流設備進行寫操作的過程,而且這個寫操作是從流的低地址向高地址開始寫。對于多字節字來說,如果先寫入高位字節,則稱作大端模式。反之則稱作小端模式。也就是說,大端模式下,字節序和流設備的地址順序是相反的,而小端模式則是相同的。一般網絡協議都采用大端模式進行傳輸。
關于亂碼
亂碼指的是程序顯示出來的字符文本無法用任何語言去解讀。造成亂碼的原因就是因為使用了錯誤的字符編碼去解碼字節流。當程序使用特定字符編碼解析字節流的時候,一旦遇到無法解析的字節流時,就會用?或者?來替代。因此,一旦你最終解析得到的文本包含這樣的字符,而你又無法得到原始字節流的時候,說明正確的信息已經徹底丟失了,嘗試任何字符編碼都無法從這樣的字符文本中還原出正確的信息來。
總結
經過上邊的介紹,我們可以大致認為,現在流行的一些編碼方案都是在兼容ASCII的基礎上來實現的。為了滿足各國家地區的更多字符的編碼需求,出現了ANSI編碼標準,但是該編碼標準在具體各地區國家的實現上是彼此不兼容的。為了滿足世界各國字符編碼的兼容性需求,Unicode定義了一個統一、完備的字符集。為了實現Unicode字符集在編碼上的需求,又誕生了UTF-8、UTF-16等等編碼方案。
總結
以上是生活随笔為你收集整理的字符集与字符编码详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 地图坐标转换(火星、谷歌、百度、腾讯、高
- 下一篇: 纸上谈兵的意思