午夜视频在线观看区二区-午夜视频在线观看视频-午夜视频在线观看视频在线观看-午夜视频在线观看完整高清在线-午夜视频在线观看网站-午夜视频在线观看亚洲天堂

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

字符編碼:從基礎到亂碼解決

freeflydom
2025年3月14日 9:46 本文熱度 122

筆者嘗試通過梳理字符編碼的核心原理,同時簡單的介紹一下常見標準,希望能夠幫助各位讀者構建對字符編碼技術的基礎認知框架。

此外本文所述均只在 Windows 下實驗。

問題的引入#

在日常開發中,當我們嘗試將中文輸出到控制臺時,點擊編譯。這時,細心的讀者可能會關注到 VS 的控制臺會輸出一段這樣的警告(也有可能是團隊規定不允許有警告出現??):

文件包含在偏移 0x9c8 處開始的字符,該字符在當前源字符集中無效(代碼頁 65001)。

同時你心心念念的中文,輸出到控制臺卻成為了亂碼。為什么會出現這種問題呢?

這一系列的問題,歸根結底,就是一個字符在計算機中,應該怎么樣來表示。也就是字符的編碼問題。所以,讓我們先來了解了解,現代計算機體系中的編碼模型是什么樣的。

這一系列問題,追根溯源,其實就是一個字符在計算機中該如何表示的問題,即字符的編碼問題。那么,我們先來了解一下現代計算機體系中的編碼模型是怎樣的。

字符編碼模型#

Unicode 字符編碼結構模型分為 5 層,下面我們以一個“漢”字為例,為大家介紹這 5 層。

抽象字符集 (Abstract Character Set) ACR#

待編碼字符集,定義字符的邏輯集合,不涉及具體的編碼邏輯。這一層僅確定“漢”字屬于某個字符集。(像 GB2312 就只收錄了 6763 個常用的漢字和字符,一些生僻字就沒有被收錄進來。又比如 ASCII 中就沒有中文字符。)

編碼字符集 (Coding Character Set) CCS

從抽象字符集(ACR)映射到一組非負整數,也就是為每一個字符分配一個唯一的二數字(碼位/碼點)。例如:Unicode、ASCII、USC、GBK等編碼。

在 Unicode 中,“漢”,表示成:\u6C49,而在 GBK 中,“漢”,表示成:0xBABA。

字符編碼表 (Character Encoding Form) CEF

一個從一組非負整數(來自 CCS)到一組特定寬度代碼單元序列的映射。我們常說的 UTF-8、UTF-16、UTF-32 就是一個字符編碼表。他規定了在抽象字符集中的“非負整數”怎么用字節表示。

例如在 UTF-8 中,“漢”字用三個字節表示:0xE6B189。

字符編碼方案 (Character Encoding Scheme) CES

一個從一組代碼單元序列(來自一個或多個 CEF)到序列化字節序列的映射。

定義碼元序列的存儲方式,解決字節序等問題:

例如:

  • UTF-8無需處理字節序(單字節碼元),直接存儲為 0xE6 0xB1 0x89

  • UTF-16若使用大端序(Big-Endian),則存儲為 FE FF 6C 49(前兩個字節為BOM標識)。

此層確保不同系統對同一編碼單元序列的解析一致性。

傳輸編碼語法 (Transfer Encoding Syntax) TES

針對特殊場景的二次編碼,如網絡傳輸:

  • 通過Base64將二進制 0xE6B189 轉換為字符串“5rGJ”

  • URL編碼將UTF-8字節轉換為 %E6%B1%89

通過上面的介紹,相信你對現代編碼模型的五層有了基本的了解。感興趣的讀者可以去看 Unicode technical report #17

講完了字符編碼模型,接下來我們來了解一些常見的字符編碼標準及其特點。

常見字符編碼 

相信大家在日常的開發中,經常聽到 Unicode、GB2312、GBK、UTF-8、UTF-16、UTF-32、ANSI,卻又對這些概念比較模糊。首先要明確一點的是,Unicode、GB2312、GBK 都是編碼字符集,而UTF-8、UTF-16、UTF-32 則是 Unicode 的編碼字符表。ANSI 比較特殊,我們待會再具體介紹。

由于篇幅限制,對各個編碼的具體編碼模式感興趣的讀者可以在參考文獻中自行了解。

ASCII#

引用自ASCII-WikipediaASCII-simple-Wikipedia

ASCII,全稱American Standard Code for Information Interchange(美國信息交換標準代碼),于 1963 年發布。標準 ASCII 采用 7 位二進制數來表示字符,因此它最多只能表示 128 個字符。?


ASCII 編碼雖然解決了英語的編碼問題,但中文怎么辦呢?漢字有那么多字。此時,就有了 GK2312 編碼。

GB2312

引用自ASCII-Wikipedia-zhASCII-Wikipedia-en

GB2312,又稱 GB/T 2312-1980,全稱信息交換用漢字編碼字符集·基本集》,與 1980 年由中國國家標準總局發布。GB2312 收錄共收錄 6763 個漢字,其中一級漢字3755個,二級漢字3008個;同時收錄了包括拉丁字母希臘字母日文平假名片假名字母、注音符號俄語西里爾字母在內的682個字符。

GB2312 使用兩個字節來表示,第一個字節稱為“高位字節”,對應分區的編號(把區位碼的“區碼”加上特定值);第二個字節稱為“低位字節”,對應區段內的個別碼位(把區位碼的“位碼”加上特定值)。

Unicode

隨著計算機技術在全世界的廣泛應用,越來越多來自不同地區,擁有不同文字的人們也加入了計算機世界,同時也帶來了越來越多的種類。在 1991 年,由一個非盈利機構 Unicode 聯盟首次發布了 The Unicode Standard,旨在統一整個計算機世界的編碼。

Unicode 的編碼空間從 U+0000 到 U+10FFFF,劃分為 17 個平面(plane),每個平面包含216 個碼位(0x0000~0xFFFF),其中第一個平面稱為基本多語言平面(Basic Multilingual Plane,BMP),其他平面稱為輔助平面(Supplementary Planes)。

具體編碼方式可以參考:徹底弄懂 Unicode 編碼

GBK

由于 GB2312 只收錄了 6763 個漢字,有一些 GB2312 推出之后才簡化的漢字,部分人用名字、繁體字等未被收錄進標準,由中華人民共和國全國信息技術標準化技術委員會1995年12月1日制訂了 GBK 編碼。GBK 共收錄 21886 個漢字和圖形符號。

UTF-8、UTF-16、UTF-32

Unicode 轉換格式(Unicode Transformation Format,簡稱 UTF),一個字符的 Unicode 編碼雖然是確定的,但是由于不同系統平臺的設計不一定一致,以及出于節省空間的目的,對 Unicode 編碼的實現方式有所不同。所以就有著不同的 Unicode 轉換格式:UTF-8、UTF-16、UTF-32。

UTF-8

UTF-8(8-bit Unicode Transformation Format)是一種用于實現Unicode的編碼方式,它使用一到四個字節來表示一個字符。UTF-8具有良好的兼容性和效率,能夠與ASCII字符集完全兼容,對于其他語言字符也能夠以較高效的方式進行編碼。

UTF-8 采用下面的規則來編碼

  • 在ASCII碼的范圍,用一個字節表示,超出ASCII碼的范圍就用字節表示,這就形成了我們上面看到的UTF-8的表示方法,這樣的好處是當UNICODE文件中只有ASCII碼時,存儲的文件都為一個字節,所以就是普通的ASCII文件無異,讀取的時候也是如此,所以能與以前的ASCII文件兼容。

  • 大于ASCII碼的,就會由上面的第一字節的前幾位表示該unicode字符的長度,比如110xxxxx前三位的二進制表示告訴我們這是個2BYTE的UNICODE字符;1110xxxx是個三位的UNICODE字符,依此類推;xxx的位置由字符編碼數的二進制表示的位填入。越靠右的x具有越少的特殊意義。只用最短的那個足夠表達一個字符編碼數的多字節串。注意在多字節串中,第一個字節的開頭"1"的數目就是整個串中字節的數目。

碼點的位數碼點起值碼點終值字節序列Byte 1Byte 2Byte 3Byte 4Byte 5Byte 6
7U+0000U+007F10xxxxxxx




11U+0080U+07FF2110xxxxx10xxxxxx



16U+0800U+FFFF31110xxxx10xxxxxx10xxxxxx


21U+10000U+1FFFFF411110xxx10xxxxxx10xxxxxx10xxxxxx

26U+200000U+3FFFFFF5111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
31U+4000000U+7FFFFFFF61111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx

UTF-8 BOM

BOM,全稱字節序標志(byte-order mark)。目的是為了表示 Unicode 編碼的字節順序。使用 BOM 模式會在文件頭處添加 U+FEFF,對應到 UTF-8 格式的文件,則會在文件起始處添加三個字節:0xEF、0xBB、0xBF。 還記得我們之前在說字符編碼方案時,說過 UTF-8 無需處理大端小端。那為什么不需要呢?

字節序(Endianness)是指多字節數據(如一個整數或一個字符的多字節表示)在內存中的存儲順序。而對于 UTF-8 中,每個使用UTF-8存儲的字符,除了第一個字節外,其余字節的頭兩個比特都是以"10"開始,除了第一個字符以外,其他都是唯一的。

但是 Unicode 標準并不要求也不推薦使用 BOM 來表示 UTF-8,但是某些軟件如果第一個字符不是 BOM (或者文件里只包含 ASCII),則拒絕正確解釋 UTF-8

UTF-16

UTF-16 把 Unicode 字符集的抽象碼位映射為 16 位長的整數(即碼元)的序列,也就是說在 UTF-16 編碼方式下,一個 Unicode 字符,需要一個或者兩個 16 位長的碼元來表示。因此 UTF-16 也是一種具體編碼。

Unicode 的基本多語言平面(BMP)內,從U+D800到U+DFFF之間的碼位區段是永久保留不映射到Unicode字符。UTF-16就利用保留下來的0xD800-0xDFFF區塊的碼位來對輔助平面的字符的碼位進行編碼。

UTF-16 采用下面的方法用來編碼:

  • 基本平面的碼點,直接用 16 比特長的單個碼元表示,數值等價于對應的碼位。

  • 輔助平面的碼點,先將碼位減去 0x10000,得到的值范圍為 20 比特長的 0x00000 ~ 0xFFFFF。其次高位的 10bit(值范圍為 0x000 ~ 0x3FF),加上 0xD800,得到第一個碼元,又稱高位代理(現代 Unicode 標準稱之為前導代理),值范圍為 0xD800 ~ 0xDBFF。再將低位的 10bit(值范圍也為 0x000 ~ 0x3FF),加上 0xDC00,得到第二個碼元,又稱低位代理(現代 Unicode 標準稱之為后尾代理),值范圍為 0xDC00 ~ 0xDFFF

同樣我們也以“漢”字為例,它在 Unicode 中為:U+6C49,處于 BMP 中,所以直接用 0x6C49 表示。而另外一個以U+10437編碼(??)為例:

  1. 0x10437 減去 0x10000,結果為0x00437,二進制為 0000 0000 0100 0011 0111

  2. 分割它的上10位值和下10位值(使用二進制):0000 0000 01  00 0011 0111

  3. 添加 0xD800 到上值,以形成高位0xD800 + 0x0001 = 0xD801

  4. 添加 0xDC00 到下值,以形成低位0xDC00 + 0x0037 = 0xDC37

UTF-32#

Unicode-32 直接采用 4 個字節來存儲 Unicode 碼位。這種編碼格式的優點是能夠直接用 Unicode 碼位來索引,但同時,相比于其他編碼(UTF-8、UTF-16),浪費空間,所以應用并不廣泛。

ANSI#

當我們創建一個文本文件,并用 Notepad++查看其默認編碼時,會看到一個 ANSI

那么 ANSI 是什么編碼呢?簡而言之,ANSI 不是某一種特定的字符編碼,而是在不同系統中,表示不同的編碼。

輸入字符集與執行字符集

  • 輸入字符集:決定了編譯器如何讀取和解析源代碼中的字符。

  • 執行字符集:決定了編譯器如何將字符和字符串常量編碼并存儲到可執行文件中。

例如:輸入字符集為GB2312時,"中文"兩個字,對應的二進制是:

而輸入字符集為UTF-8時則為下面:

而執行字符集,可以通過顯示設置字符集來修改:

在編譯器中顯式設置輸入字符集和執行字符集。對于GCC編譯器,可以使用 -finput-charset=UTF-8 -fexec-charset=UTF-8 選項;對于MSVC編譯器,可以使用 /source-charset:utf-8 /execution-charset:utf-8 選項,你也可以使用 /utf-8來指定輸入字符集和執行字符集都為 UTF-8。

如果輸入字符集和執行字符集不一致,編譯器需要在編譯過程中進行字符編碼的轉換。當兩者不一致時,編譯器需進行編碼轉換,可能引發:

  • 字符映射丟失(如GBK→ASCII)

  • 字節序列錯誤(如UTF-8→UTF-16LE)

所以,盡量將兩個字符集設置成一樣的。

代碼頁

在計算機發展的早期階段,ASCII編碼(美國信息交換標準代碼)是主流的字符編碼方式,它使用7位二進制數表示128個字符,包括英文字母、數字和一些標點符號。然而,ASCII編碼無法滿足多語言環境的需求,因為世界上有成千上萬種語言和符號。

為了解決這個問題,操作系統和軟件開發商引入了代碼頁的概念。代碼頁允許系統支持多種字符集,尤其是那些超出ASCII范圍的語言字符。在Windows操作系統中,代碼頁是系統用來處理文本數據的機制。例如,當用戶在系統中輸入或顯示文本時,系統會根據當前的代碼頁設置來解釋這些字符。

假設你有一個文本文件,內容是中文字符“你好”。如果這個文件是用GBK編碼保存的,那么它的字節序列可能是 C4 E3 BA C3。操作系統會根據代碼頁936(GBK)來解釋這些字節,并正確顯示為“你好”。但如果系統錯誤地使用了代碼頁1252(西歐字符集),這些字節會被解釋為亂碼,因為代碼頁1252中沒有對應的字符。

再探亂碼

看到這里,相信各位讀者對字符編碼已經有些一些基礎的了解。所以,下面讓我們嘗試解答剛開始提出的問題:

  1. 為什么 std::cout << "中文" << std::endl; 輸出到控制臺會亂碼?

  2. 該字符在當前源字符集中無效(代碼頁65001)

為什么控制臺會輸出亂碼?

假設有這樣一段代碼:

// main.cpp
#include <iostream>
int main(int argc, char** argv){
    std::cout << "中文" << std::endl;
    return 0;
}

運行起來后,會發現輸出到控制臺是這種情況:

這個問題的影響因素有兩個:

  • 控制臺字符編碼

  • 文件源字符集

首先,在 Windows 下,控制臺的默認編碼是當前系統的代碼頁(通常是 GB2312),所以如果你輸出到控制臺的字符不是當前代碼頁編碼對應的字符,那么就會發生亂碼。當前系統的代碼頁通過 cmd 執行命令 chcp來查看。 假如文件的源格式是 UTF-8,那么"中文"這兩個字的字節序列為:

當我們輸出到控制臺時,按照 GB2312 編碼去解析這 6 個字節時,我們會得到:

涓(E4B8)(ADE6)枃(9687),其中 ADE6 在 GB2312 中為錯誤編碼,所以會顯示一個問號。

根據這個思路,我們有兩種方法解決這個問題:

  • 修改控制臺字符編碼

  • 修改源文件字符集

第一種我們通過執行 chcp 來修改當前代碼頁:

// main.cpp
#include <iostream>
int main(int argc, char** argv){
    // 65001 代表UTF-8
    system("chcp 65001");
    std::cout << "中文" << std::endl;
    return 0;
}

第二種,就是修改文件的字符編碼格式,改成 GB2312。怎么改我就不贅述了,網上一大把。

該字符在當前源字符集中無效?

這一個問題與輸入字符集有關,當文件編碼與編譯器預期不一致,例如你的文件是GB2312編碼,但編譯器(如MSVC)默認使用UTF-8(代碼頁65001)來解析源文件。GB2312和UTF-8是不兼容的編碼格式,導致編譯器無法正確解析文件中的字符。

筆者的 Visual Studio 工程命令行有一個 /utf-8,也就代表輸入、執行編碼集都為 utf-8。所以,當你文件的編碼為 GB2312 時,

  1. “創”字的GB2312編碼在GB2312編碼中,“創”字的編碼是 0xD4 0xB4

  2. “創”字的UTF-8編碼在UTF-8編碼中,“創”字的編碼是 0xE5 0x8D 0x94

  3. 當編譯器以UTF-8編碼解析文件時,會將 GB2312編碼的字節序列 0xD4 0xB4 視為一個潛在的UTF-8字符。然而,根據UTF-8的編碼規則: 0xD4 是一個以 1101 開頭的字節,表示這是一個兩字節字符,第一個字節的格式應為 110xxxxx ,第二個字節的格式應為 10xxxxxx 。但是, 0xD4 的二進制是 11010100 ,而 0xB4 的二進制是 10110100 。

雖然第二個字節符合 10xxxxxx 的格式,但第一個字節的值 0xD4 超出了UTF-8兩字節字符的合法范圍( 0xC0 到 0xDF ),因此整個字節序列 0xD4 0xB4 是無效的UTF-8字符。

QString 一些字符相關的函數

在 QString 中有許多的轉換函數:

  1. QString::fromLatin1

  2. QString::fromLocal8Bit

  3. QString::fromUtf8

  4. QString::fromWCharArray

QString 是以 UTF-16 的格式存儲的字符:

QString stores a string of 16-bit QChars, where each QChar corresponds to one UTF-16 code unit.

所以,調用上面這些函數就是用指定的格式讀取字符,并將這些字符轉換成 UTF-16 格式。參看下面的例子:

    QString str("中文");
    qDebug() << str;
    qDebug() << QStringLiteral("2中文");
    qDebug() << QString::fromLatin1("3中文");             // Latin-1 ≈ ASCII
    qDebug() << QString::fromLocal8Bit("4中文");          // Windows下取決于當前代碼頁 一般中文系統是:GBK
    qDebug() << QString::fromUtf8("5中文");               // UTF-8
    qDebug() << QString::fromWCharArray(L"6中文");        // Returns a copy of the string, where the encoding of string depends on the size of wchar. 
                                                          // If wchar is 4 bytes, the string is interpreted as UCS-4,
                                                          // if wchar is 2 bytes it is interpreted as UTF-16.

輸入字符集為GB2312時:

輸入字符集為UTF-8時:

最后的最后#

感謝各位讀者閱讀本博客,本博客內容在創作過程中,參考了大量百科知識以及其他優秀博客,并結合筆者自身在實際工作中遇到的相關問題。筆者希望通過這篇博客,能為各位讀者在字符編碼這一塊提供一些有價值的見解和幫助。

轉自https://www.cnblogs.com/codegb/p/18768600


該文章在 2025/3/14 9:53:11 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 国产一区二区视频 | 国产精品无码av片在线观看播 | 91在线无码精品 | 国产成人无码aⅴ片在线观看视频 | 高清一区二区亚洲欧美日韩 | 精品欧美国产一区二区三区 | av永久网站免费观看 | 精品日韩人伦一区二区三区蜜桃 | 囯产精品一区二区免费在线观看 | 国产高清又黄又爽又刺激视频 | 2025亚洲欧美日韩在线观看 | 大帝av在线一区二区三区 | av片免费看 | 1024国产精品视频一区 | 国产99久久九九精品黑人 | 国产一本大道视频在线观看 | 国产成人午夜福利在线观看者 | 国产成人三级在线播放 | 国产精品一区三区 | 国产精品一区免费视频播放 | 国产一区二区精品久久岳 | 国产精品福利午夜在线观看 | 91夜色精品偷窥熟女精品网站 | 精品国产一区二区三区2025 | 国产高清日韩在线播放 | 高清无码在线观看视频 | 东京热一区二区沙河无码网站 | 国产午夜片无码区在线观看爱情 | 91精品啪在线观看国产色 | 国产亚洲自拍一区在线观看 | 97色伦午 | 加勒比色老久久综合网 | 国产91高跟丝袜 | hezyo加勒比久久爱综合 | 国产午夜成人免费看片无遮挡 | 国产精品线观看 | 国产精品国产三级国av麻豆 | 国产高清一区二区三区免 | 国产免费无码秘一区二区三区 | 97人妻无码公开免费 | 国产av午夜福利写真电影 |