轉載:從程式員的角度看ASCII, GB2312, UNICODE, UTF-8

来源:http://www.cnblogs.com/zhongtang/archive/2016/06/16/5591808.html
-Advertisement-
Play Games

以下內容轉自博客:http://blog.chinaunix.net/uid-22670933-id-1771613.html。 一、字元編碼是怎麼回事 0. 概念 位元組是電腦的最基本存儲單位,一個位元組包括8個位. 字元是一種文字的基本單位,比如'A' 是一個字元,'漢' 也是一個字元. 1. 計 ...


以下內容轉自博客:http://blog.chinaunix.net/uid-22670933-id-1771613.html

一、字元編碼是怎麼回事

0. 概念

位元組是電腦的最基本存儲單位,一個位元組包括8個位.

字元是一種文字的基本單位,比如'A' 是一個字元,'漢' 也是一個字元.

 

1. 電腦被髮明之後,程式員們編寫了很多複雜的計算讓電腦運行.

但是一個問題是,電腦如何把辛苦計算的結果告知程式員? 假設電腦把計算結果放在某個寄存器,內容是 1010010

總不能讓程式員去檢測每個引腳的電位吧? 還是得有個顯示器.

 

顯示器是依靠點陣來顯示圖像的. CPU必須告訴顯示器,當CPU 把一個位元組的數據比如 00101010 放入顯示器寄存器的時候,顯示器要顯示怎樣的一個點陣(圖像),這個圖像就是我們人類可以看得懂的字元. 這樣問題就解決了,比如當CPU把00110001放入顯示器寄存器時,顯示器就顯示(控制點陣畫出一個圖像-字體)字元 "1", 這是一個查表的過程,記憶體中的值(內碼)和字元是一一對應的. 問題是這個對應關係是可以自由確定的,我可以指定顯示器把 00110001(內碼) 顯示為字元 "1",也可以指定顯示為字元"2". 這樣當然會引起混亂,同一個內碼被映射為不同的字元,不利於人們的交流.

 

美國國家標準學會(ANSI)決定著手解決這個問題, 英語有一個很小的字元集,26個字母再加上一些控制字元和標點符號,7位2進位值就足以表示所有的變化.於是ANSI公佈了一個標準的對應關係,以位元組為單位. 當內碼為 0110001 時,大家都公認它代表字元 "1",在所有顯示器都顯示為同一個字元. 這樣大家就可以按照同一個標準相互交換數據而不會引起誤解. 這個表就是一個包含了128項的對應關係, 叫做 "ASCII", 美國信息交換標準代碼.

2.對於中國這樣不使用ABC字元的國家來說,如何顯示自己的文字是一個大問題.

 

我們可以制定一個內碼表,指定一個內碼對應一個漢字. (由於中文的字元非常多,所以一個位元組是不夠的,至少也要有2個位元組存儲一個內碼.) 這是很容易的,只要國家公佈一個標準的內碼字元對應表,大家都遵照這個表就可以了.但是還是有一些問題要註意:

(1). 即使在中國,電腦還是得能顯示英文吧?

而英文的內碼已經有 ASCII 標準在先,並且已經有無數的程式已經在這個標準上運行了很多年,成為不可或缺的部分. 所以我們新制定的內碼表必須和 ASCII 相容.

(2) 很多C語言的庫函數是以內碼0作為字元串結束標誌的,為了相容那些以前就已經編寫好,並且運行良好的程式,我們指定的內碼中不能含有值為0的位元組.

巧合的是,所有的ASCII內碼的最高位都為0. 那麼我們只要讓第一個和第二個位元組(一個漢字占用2個位元組)的最高位都為1,這樣既和ASCII內碼區分開來,又不會出現0.符合這個規則的內碼(2位元組長)理論上一共可以標識 127 * 127 = 16129個字元(實際上只用了6000多個碼位,保留了一些,不過也已經夠用了,常用的中文字元只有4000多個).

我們國家公佈的這個內碼標準表就是GB2312. 

原有的英文軟體可以很好的運行,C的庫函數也不用做修改, 比如 strlen("ABC") 在GB2312表示的內碼中, 由於GB2312對英文字元的編碼是和ASCII完全一樣的,所以返回

3.對於 strlen("A漢字"), 由於strlen()是以內碼為0作為邊界的,而所有中文字元的GB2312內碼高位都為1,不會出現0,並且每個漢字占用2個位元組,所以 strlen 返回5. 對於程式來說只要檢查一個位元組的最高位,就可以很容易的判斷這個字元是中文還是英文字元,非常方便.

 

"一個字母一個位元組,一個漢字2個位元組" 的觀念深入人心.

有了GB2312之後,漢字顯示/存儲/交換就基本上沒什麼問題了.

幾乎所有的非英語國家都制定了和GB2312類似相容ASCII的內碼字元對應表.

(BIG5 由於有幾個字元的內碼和ASCII相同但表示不同的字元,不符合2.(1)條件.所以被認為是有"瑕疵"的.) 

 

3. 很明顯,GB2312的碼位是不夠的, 一個例子就是有很多人的人名電腦里打不出來.(只有6000多個碼位,而<<康熙字典>>就收錄了4萬多個漢字).所以後來有出現了諸如GBK, GB18030以及同期流行於臺灣香港的BIG5編碼. 雖然編碼有些不同, 但是設計思想是一致的: 相容ASCII,並確保不會有某個位元組值為0的內碼出現.有一個共同的特點是: 它們都是局部的標準,只流行於某個地區/國家內.

 

4.由於內碼表都是各個國家獨自製定的,同一個內碼,在不同的國家表示的可能是不同的字元.(除了ASCII字元, ASCII字元在所有國家指定的內碼表中都有同樣的值.)不利於國家間的信息交換. 於是 UNICODE 應運而生.

UNICODE 採用一種很簡單的辦法來解決這個問題. 就是採用2個 - UCS-2 (或者4個位元組 - UCS-4)位元組標識一個字元. 2個位元組總共可以表示65535個字元,足夠表示世界上的所有語言的所有字元.(漢字不就有4萬多個嗎,65535怎麼夠. 我估計只是常用的漢字幾千個被編在UCS-2中吧. 目前被正式編碼到UNICODE碼位的只有不超過65534個, 所以就目前的情況來說,用2個位元組是可以的.) 註意 UCS-4, UCS-2 和 ASCII是向下相容的,只要前面補0就可以了.這點很重要,可以一直擴展下去包含全宇宙的字元.

現在地球上每個字元在所有採用UNICODE字元編碼的電腦內都有一個唯一的內碼了.

要註意, 除了ASCII字元外,其他國家文字的字元的內碼是重新分配過的,不一定和各國原有的編碼相同.比如大部分漢字的GB2312內碼和UNICODE 內碼都是不同的.

 

5. 很顯然,對於英語國家來說,UNICODE內碼非常浪費空間,對於UCS-2 浪費了50%的存儲空間,對於 UCS-4 則浪費了70%的存儲空間. 而且還有一個更大的問題, UNICODE的內碼中含有很多 '\0', 原有的C標準庫函數沒辦法處理這些字元串.於是有人發明瞭一種針對UNICODE的變換規則,把UNICODE字元串中的0去除. 註意這個變換規則不是通過查表實現的,而只要用一些位移操作就可以實現. 這就是UTF8.

 

總結:

UTF8 只是 UNICODE內碼在存儲/傳輸時的狀態. 而從GB2312編碼轉換到UNICODE編碼需要查表. UTF8 和 UNICODE 的關係 與 GB2312 和 UNICODE的關係有本質的不同. UTF8 和 UNICODE 是一個人的兩個面孔, GB2312 和 UNICODE 是兩個人.

所以,要實現UTF8編碼到GB2312編碼的轉換必須先把 UTF8編碼還原為UNICODE編碼,再通過查表的方式,把UNICODE編碼轉化為GB2312編碼.

以上,雖然說得不是很嚴謹(比如GB2312其實是區位碼,真正的內碼還要給每個位元組加上A0, 這些我都沒提,免得分散註意力),但是文字編碼的原理大致就是那麼回事,理解就好了. 要想詳細瞭解細節Google一下能找到很多資料.

 

二、字元編碼的編程相關問題

1. Windows從NT開始,內核使用UNICODE內碼. 為了向前相容,前端使用的還是GB2312內碼(中文環境). 

所以用 Visual Studio 編寫代碼時, 如果在CPP文件中寫這樣一句 const char* pszText = "中文", 編譯器讓 pszText 指向"中文"的GB2312內碼值的記憶體空間. 當調用 printf(pszText)時, WINAPI 把這個GB2312字元串轉化為UNICODE字元串再輸出.(WIndows自然知道你的編碼是GB2312,因為你在Windows系統中設置的語言區域是中國, CodePage 936. 如果改成其它語言,就會顯示為亂碼.)

微軟非常鼓勵Windows程式員用Unicode編寫程式,很明顯,由於Windows內核就是原生的Unicode環境,調用API時,省卻了編碼轉換的操作,效率更高. 而且一個額外的好處就是不會有亂碼. 註意,MS的C/C++編譯器把sizeof(wchar_t)設置為2個位元組. 由於目前所有的UNICODE字元只有65534個碼位(BMP),所以用2個位元組是沒問題的.

 

2. Linux系統(比如Ubuntu)現在一般都用UTF8編碼了.

我們在Linux下創建CPP文件並添加同樣的: const char* pszText = "中文" 編譯器會讓 pszText 指向"中文"UTF8的內碼值的記憶體空間.Linux的終端可以理解為一個只接收UTF8字元串的顯示器. 任何被寫到終端的字元流都被認為是是一個UTF8字元流.所以,編程的時候,從外部(文件或者控制台)讀入UTF8字元流,轉換為wchar_t,然後程式在內部使用寬字元處理,最後再把要輸出的寬字元流轉換為UTF8字元流並輸出到控制台/文件中. 用戶程式可以通過環境變數LANG的值得知當前的系統環境所使用的字元編碼.由此可見,C庫函數的 mbstowcs()/wcstombs()主要是為應付這種情況設計的. 如果要處理XML, HTML 等等有明確指明字元編碼的字元流,用專門的字元轉換庫更為方便.

為什麼很多Windows下的C源文件的註釋在Linux編輯器下會顯示為亂碼就很好理解了.

 

3. 字元編碼轉換相關的函數和庫

 

Windows 的字元轉換函數: MultiByteToWideChar() / WideCharToMultiByte()

Linux 的字元轉換庫: GLIBC iconv函數組.

 

C標準庫使用的 mbstowcs()和wcstombs()和 locale 相關,用起來很不方便,而且功能有限.

 

(註意不要假設 wchar_t 的大小, 它可能是4位元組也可能是2位元組,取決於編譯器. 比如 MS VC9.0 (2008) 里, sizeof(wchar_t) = 2, 而在GCC中, sizeof(wchar_t) = 4.)

 

4. 給定一個ANSI相容的字元串(包括GB2312,GBK,UTF8等),無法確定它的編碼類型,只能猜測.所以不要指望會有一個萬能的轉換函數.

 

5. BOM (Byte Order Mark)UNICODE: FF FE / FE FF 和 UTF8: EF BB BF 是不完全靠譜的,僅供參考.

 

最後說明一點,對於不是專門處理字元編碼的程式來說,所有字元編碼相關的問題只是顯示的問題,並不會影響到程式的內在邏輯.

開始用 Unicode 來編寫我們的代碼吧.

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文目錄 Asp.net Core 對於授權的改動很友好,非常的靈活,本文以MVC為主,當然如果說webapi或者其他的分散式解決方案授權,也容易就可以實現單點登錄都非常的簡單,可以使用現成的IdentityServer框架或者自定義實現動非常方便和乾凈,如果你在運行示例代碼的時候未達到預期效果,請 ...
  • 冒泡排序 實現原理 ① 首先將所有待排序的數字放入工作列表中。 ② 從列表的第一個數字到倒數第二個數字,逐個檢查:若某一位上的數字大於他的下一位,則將它與它的下一位交換。 ③ 重覆步驟②,直至再也不能交換。 代碼實現 1 <?php 2 function bubbingSort(array $arr ...
  • 用Qt做ARM,發現Qt4中QTabWidget原生的關閉按鍵(X)太小,用觸摸板很難按到。於是乎想到類似於瀏覽器的雙擊關閉功能,因為之前做過C#的資源管理器,以為可以直接綁定DoubleClick,可後來翻遍了也沒找到相應的SLOT,結果在QWidget中捕捉信號,就是沒有QTabWidget標簽 ...
  • 【正文】 面試必問關鍵詞:JVM垃圾回收、類載入機制。 先把本文的目錄畫一個思維導圖:(圖的源文件在本文末尾) 一、Java引用的四種狀態: 強引用: 用的最廣。我們平時寫代碼時,new一個Object存放在堆記憶體,然後用一個引用指向它,這就是強引用。 如果一個對象具有強引用,那垃圾回收器絕不會回收 ...
  • PHP 全局變數 PHP中預定義了幾個超級全局變數(superglobals) ,這意味著它們在一個腳本的全部作用域中都可用。 你不需要特別說明,就可以在函數及類中使用。 PHP 超級全局變數列表: $GLOBALS $_SERVER $_REQUEST $_POST $_GET $_FILES $ ...
  • 在scala里,類繼承有兩點限制: 重寫方法需要使用override關鍵字; 只有主構造函數才能往父類構造函數中傳參數。 在java1.5中引入了override註解,但不強制使用。不過在scala中要想重寫方法必須使用override關鍵字。如果確實重寫了父類的方法又不使用override關鍵字的... ...
  • 最早見過手寫的,類似於下麵這種: 輸出如下: 另外一種方法是使用timeit模塊,使用方法如下: 還可以在命令行上使用這種timeit模塊,如下: 註意:timeit模塊會多次運行程式以獲得更精確的時間,所以需要避免重覆執行帶來的影響。比方說x.sort()這種操作,因為第一次執行之後,後邊已經是排 ...
  • 我想學ruby以後開髮網站,但ruby是高級語言,隱藏了許多底層的東西,因此先熟悉c語言 首先c程式的文件名是以.c結尾的 c程式的格式: 第一行#include<stdio.h> #是一個預處理標準,用來對文本進行預處理操作,表示該行代碼要最先進行處理,在編譯代碼之前運行 include是一個指令 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...