字符編碼:從基礎(chǔ)到亂碼解決
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
問(wèn)題的引入#在日常開(kāi)發(fā)中,當(dāng)我們嘗試將中文輸出到控制臺(tái)時(shí),點(diǎn)擊編譯。這時(shí),細(xì)心的讀者可能會(huì)關(guān)注到 VS 的控制臺(tái)會(huì)輸出一段這樣的警告(也有可能是團(tuán)隊(duì)規(guī)定不允許有警告出現(xiàn)??):
同時(shí)你心心念念的中文,輸出到控制臺(tái)卻成為了亂碼。為什么會(huì)出現(xiàn)這種問(wèn)題呢? 這一系列的問(wèn)題,歸根結(jié)底,就是一個(gè)字符在計(jì)算機(jī)中,應(yīng)該怎么樣來(lái)表示。也就是字符的編碼問(wèn)題。所以,讓我們先來(lái)了解了解,現(xiàn)代計(jì)算機(jī)體系中的編碼模型是什么樣的。 這一系列問(wèn)題,追根溯源,其實(shí)就是一個(gè)字符在計(jì)算機(jī)中該如何表示的問(wèn)題,即字符的編碼問(wèn)題。那么,我們先來(lái)了解一下現(xiàn)代計(jì)算機(jī)體系中的編碼模型是怎樣的。 字符編碼模型#Unicode 字符編碼結(jié)構(gòu)模型分為 5 層,下面我們以一個(gè)“漢”字為例,為大家介紹這 5 層。 抽象字符集 (Abstract Character Set) ACR#待編碼字符集,定義字符的邏輯集合,不涉及具體的編碼邏輯。這一層僅確定“漢”字屬于某個(gè)字符集。(像 GB2312 就只收錄了 6763 個(gè)常用的漢字和字符,一些生僻字就沒(méi)有被收錄進(jìn)來(lái)。又比如 ASCII 中就沒(méi)有中文字符。) 編碼字符集 (Coding Character Set) CCS從抽象字符集(ACR)映射到一組非負(fù)整數(shù),也就是為每一個(gè)字符分配一個(gè)唯一的二數(shù)字(碼位/碼點(diǎn))。例如:Unicode、ASCII、USC、GBK等編碼。 在 Unicode 中,“漢”,表示成:\u6C49,而在 GBK 中,“漢”,表示成:0xBABA。 字符編碼表 (Character Encoding Form) CEF一個(gè)從一組非負(fù)整數(shù)(來(lái)自 CCS)到一組特定寬度代碼單元序列的映射。我們常說(shuō)的 UTF-8、UTF-16、UTF-32 就是一個(gè)字符編碼表。他規(guī)定了在抽象字符集中的“非負(fù)整數(shù)”怎么用字節(jié)表示。 例如在 UTF-8 中,“漢”字用三個(gè)字節(jié)表示:0xE6B189。 字符編碼方案 (Character Encoding Scheme) CES一個(gè)從一組代碼單元序列(來(lái)自一個(gè)或多個(gè) CEF)到序列化字節(jié)序列的映射。 定義碼元序列的存儲(chǔ)方式,解決字節(jié)序等問(wèn)題: 例如:
此層確保不同系統(tǒng)對(duì)同一編碼單元序列的解析一致性。 傳輸編碼語(yǔ)法 (Transfer Encoding Syntax) TES針對(duì)特殊場(chǎng)景的二次編碼,如網(wǎng)絡(luò)傳輸:
通過(guò)上面的介紹,相信你對(duì)現(xiàn)代編碼模型的五層有了基本的了解。感興趣的讀者可以去看 Unicode technical report #17。 講完了字符編碼模型,接下來(lái)我們來(lái)了解一些常見(jiàn)的字符編碼標(biāo)準(zhǔn)及其特點(diǎn)。 常見(jiàn)字符編碼相信大家在日常的開(kāi)發(fā)中,經(jīng)常聽(tīng)到 Unicode、GB2312、GBK、UTF-8、UTF-16、UTF-32、ANSI,卻又對(duì)這些概念比較模糊。首先要明確一點(diǎn)的是,Unicode、GB2312、GBK 都是編碼字符集,而UTF-8、UTF-16、UTF-32 則是 Unicode 的編碼字符表。ANSI 比較特殊,我們待會(huì)再具體介紹。 由于篇幅限制,對(duì)各個(gè)編碼的具體編碼模式感興趣的讀者可以在參考文獻(xiàn)中自行了解。 ASCII#ASCII,全稱(chēng)American Standard Code for Information Interchange(美國(guó)信息交換標(biāo)準(zhǔn)代碼),于 1963 年發(fā)布。標(biāo)準(zhǔn) ASCII 采用 7 位二進(jìn)制數(shù)來(lái)表示字符,因此它最多只能表示 128 個(gè)字符。? ![]() ASCII 編碼雖然解決了英語(yǔ)的編碼問(wèn)題,但中文怎么辦呢?漢字有那么多字。此時(shí),就有了 GK2312 編碼。 GB2312GB2312,又稱(chēng) GB/T 2312-1980,全稱(chēng)《信息交換用漢字編碼字符集·基本集》,與 1980 年由中國(guó)國(guó)家標(biāo)準(zhǔn)總局發(fā)布。GB2312 收錄共收錄 6763 個(gè)漢字,其中一級(jí)漢字3755個(gè),二級(jí)漢字3008個(gè);同時(shí)收錄了包括拉丁字母、希臘字母、日文平假名及片假名字母、注音符號(hào)、俄語(yǔ)西里爾字母在內(nèi)的682個(gè)字符。 GB2312 使用兩個(gè)字節(jié)來(lái)表示,第一個(gè)字節(jié)稱(chēng)為“高位字節(jié)”,對(duì)應(yīng)分區(qū)的編號(hào)(把區(qū)位碼的“區(qū)碼”加上特定值);第二個(gè)字節(jié)稱(chēng)為“低位字節(jié)”,對(duì)應(yīng)區(qū)段內(nèi)的個(gè)別碼位(把區(qū)位碼的“位碼”加上特定值)。 Unicode隨著計(jì)算機(jī)技術(shù)在全世界的廣泛應(yīng)用,越來(lái)越多來(lái)自不同地區(qū),擁有不同文字的人們也加入了計(jì)算機(jī)世界,同時(shí)也帶來(lái)了越來(lái)越多的種類(lèi)。在 1991 年,由一個(gè)非盈利機(jī)構(gòu) Unicode 聯(lián)盟首次發(fā)布了 The Unicode Standard,旨在統(tǒng)一整個(gè)計(jì)算機(jī)世界的編碼。 Unicode 的編碼空間從 具體編碼方式可以參考:徹底弄懂 Unicode 編碼 GBK由于 GB2312 只收錄了 6763 個(gè)漢字,有一些 GB2312 推出之后才簡(jiǎn)化的漢字,部分人用名字、繁體字等未被收錄進(jìn)標(biāo)準(zhǔn),由中華人民共和國(guó)全國(guó)信息技術(shù)標(biāo)準(zhǔn)化技術(shù)委員會(huì)1995年12月1日制訂了 GBK 編碼。GBK 共收錄 21886 個(gè)漢字和圖形符號(hào)。 UTF-8、UTF-16、UTF-32Unicode 轉(zhuǎn)換格式(Unicode Transformation Format,簡(jiǎn)稱(chēng) UTF),一個(gè)字符的 Unicode 編碼雖然是確定的,但是由于不同系統(tǒng)平臺(tái)的設(shè)計(jì)不一定一致,以及出于節(jié)省空間的目的,對(duì) Unicode 編碼的實(shí)現(xiàn)方式有所不同。所以就有著不同的 Unicode 轉(zhuǎn)換格式:UTF-8、UTF-16、UTF-32。 UTF-8UTF-8(8-bit Unicode Transformation Format)是一種用于實(shí)現(xiàn)Unicode的編碼方式,它使用一到四個(gè)字節(jié)來(lái)表示一個(gè)字符。UTF-8具有良好的兼容性和效率,能夠與ASCII字符集完全兼容,對(duì)于其他語(yǔ)言字符也能夠以較高效的方式進(jìn)行編碼。 UTF-8 采用下面的規(guī)則來(lái)編碼
UTF-8 BOMBOM,全稱(chēng)字節(jié)序標(biāo)志(byte-order mark)。目的是為了表示 Unicode 編碼的字節(jié)順序。使用 BOM 模式會(huì)在文件頭處添加 字節(jié)序(Endianness)是指多字節(jié)數(shù)據(jù)(如一個(gè)整數(shù)或一個(gè)字符的多字節(jié)表示)在內(nèi)存中的存儲(chǔ)順序。而對(duì)于 UTF-8 中,每個(gè)使用UTF-8存儲(chǔ)的字符,除了第一個(gè)字節(jié)外,其余字節(jié)的頭兩個(gè)比特都是以"10"開(kāi)始,除了第一個(gè)字符以外,其他都是唯一的。 但是 Unicode 標(biāo)準(zhǔn)并不要求也不推薦使用 BOM 來(lái)表示 UTF-8,但是某些軟件如果第一個(gè)字符不是 BOM (或者文件里只包含 ASCII),則拒絕正確解釋 UTF-8。 UTF-16UTF-16 把 Unicode 字符集的抽象碼位映射為 16 位長(zhǎng)的整數(shù)(即碼元)的序列,也就是說(shuō)在 UTF-16 編碼方式下,一個(gè) Unicode 字符,需要一個(gè)或者兩個(gè) 16 位長(zhǎng)的碼元來(lái)表示。因此 UTF-16 也是一種具體編碼。 Unicode 的基本多語(yǔ)言平面(BMP)內(nèi),從U+D800到U+DFFF之間的碼位區(qū)段是永久保留不映射到Unicode字符。UTF-16就利用保留下來(lái)的0xD800-0xDFFF區(qū)塊的碼位來(lái)對(duì)輔助平面的字符的碼位進(jìn)行編碼。 UTF-16 采用下面的方法用來(lái)編碼:
同樣我們也以“漢”字為例,它在 Unicode 中為:U+6C49,處于 BMP 中,所以直接用 0x6C49 表示。而另外一個(gè)以U+10437編碼(??)為例:
UTF-32#Unicode-32 直接采用 4 個(gè)字節(jié)來(lái)存儲(chǔ) Unicode 碼位。這種編碼格式的優(yōu)點(diǎn)是能夠直接用 Unicode 碼位來(lái)索引,但同時(shí),相比于其他編碼(UTF-8、UTF-16),浪費(fèi)空間,所以應(yīng)用并不廣泛。 ANSI#當(dāng)我們創(chuàng)建一個(gè)文本文件,并用 Notepad++查看其默認(rèn)編碼時(shí),會(huì)看到一個(gè) ANSI 那么 ANSI 是什么編碼呢?簡(jiǎn)而言之,ANSI 不是某一種特定的字符編碼,而是在不同系統(tǒng)中,表示不同的編碼。 輸入字符集與執(zhí)行字符集
例如:輸入字符集為GB2312時(shí),"中文"兩個(gè)字,對(duì)應(yīng)的二進(jìn)制是: 而輸入字符集為UTF-8時(shí)則為下面: 而執(zhí)行字符集,可以通過(guò)顯示設(shè)置字符集來(lái)修改: 在編譯器中顯式設(shè)置輸入字符集和執(zhí)行字符集。對(duì)于GCC編譯器,可以使用 如果輸入字符集和執(zhí)行字符集不一致,編譯器需要在編譯過(guò)程中進(jìn)行字符編碼的轉(zhuǎn)換。當(dāng)兩者不一致時(shí),編譯器需進(jìn)行編碼轉(zhuǎn)換,可能引發(fā):
所以,盡量將兩個(gè)字符集設(shè)置成一樣的。 代碼頁(yè)在計(jì)算機(jī)發(fā)展的早期階段,ASCII編碼(美國(guó)信息交換標(biāo)準(zhǔn)代碼)是主流的字符編碼方式,它使用7位二進(jìn)制數(shù)表示128個(gè)字符,包括英文字母、數(shù)字和一些標(biāo)點(diǎn)符號(hào)。然而,ASCII編碼無(wú)法滿(mǎn)足多語(yǔ)言環(huán)境的需求,因?yàn)槭澜缟嫌谐汕先f(wàn)種語(yǔ)言和符號(hào)。 為了解決這個(gè)問(wèn)題,操作系統(tǒng)和軟件開(kāi)發(fā)商引入了代碼頁(yè)的概念。代碼頁(yè)允許系統(tǒng)支持多種字符集,尤其是那些超出ASCII范圍的語(yǔ)言字符。在Windows操作系統(tǒng)中,代碼頁(yè)是系統(tǒng)用來(lái)處理文本數(shù)據(jù)的機(jī)制。例如,當(dāng)用戶(hù)在系統(tǒng)中輸入或顯示文本時(shí),系統(tǒng)會(huì)根據(jù)當(dāng)前的代碼頁(yè)設(shè)置來(lái)解釋這些字符。 假設(shè)你有一個(gè)文本文件,內(nèi)容是中文字符“你好”。如果這個(gè)文件是用GBK編碼保存的,那么它的字節(jié)序列可能是 再探亂碼看到這里,相信各位讀者對(duì)字符編碼已經(jīng)有些一些基礎(chǔ)的了解。所以,下面讓我們嘗試解答剛開(kāi)始提出的問(wèn)題:
為什么控制臺(tái)會(huì)輸出亂碼?假設(shè)有這樣一段代碼:
運(yùn)行起來(lái)后,會(huì)發(fā)現(xiàn)輸出到控制臺(tái)是這種情況: 這個(gè)問(wèn)題的影響因素有兩個(gè):
首先,在 Windows 下,控制臺(tái)的默認(rèn)編碼是當(dāng)前系統(tǒng)的代碼頁(yè)(通常是 GB2312),所以如果你輸出到控制臺(tái)的字符不是當(dāng)前代碼頁(yè)編碼對(duì)應(yīng)的字符,那么就會(huì)發(fā)生亂碼。當(dāng)前系統(tǒng)的代碼頁(yè)通過(guò) cmd 執(zhí)行命令 當(dāng)我們輸出到控制臺(tái)時(shí),按照 GB2312 編碼去解析這 6 個(gè)字節(jié)時(shí),我們會(huì)得到: 涓(E4B8)(ADE6)枃(9687),其中 ADE6 在 GB2312 中為錯(cuò)誤編碼,所以會(huì)顯示一個(gè)問(wèn)號(hào)。 根據(jù)這個(gè)思路,我們有兩種方法解決這個(gè)問(wèn)題:
第一種我們通過(guò)執(zhí)行
第二種,就是修改文件的字符編碼格式,改成 GB2312。怎么改我就不贅述了,網(wǎng)上一大把。 該字符在當(dāng)前源字符集中無(wú)效?這一個(gè)問(wèn)題與輸入字符集有關(guān),當(dāng)文件編碼與編譯器預(yù)期不一致,例如你的文件是GB2312編碼,但編譯器(如MSVC)默認(rèn)使用UTF-8(代碼頁(yè)65001)來(lái)解析源文件。GB2312和UTF-8是不兼容的編碼格式,導(dǎo)致編譯器無(wú)法正確解析文件中的字符。 筆者的 Visual Studio 工程命令行有一個(gè)
雖然第二個(gè)字節(jié)符合 10xxxxxx 的格式,但第一個(gè)字節(jié)的值 QString 一些字符相關(guān)的函數(shù)在 QString 中有許多的轉(zhuǎn)換函數(shù):
QString 是以 UTF-16 的格式存儲(chǔ)的字符:
所以,調(diào)用上面這些函數(shù)就是用指定的格式讀取字符,并將這些字符轉(zhuǎn)換成 UTF-16 格式。參看下面的例子:
輸入字符集為GB2312時(shí): 輸入字符集為UTF-8時(shí): 最后的最后#感謝各位讀者閱讀本博客,本博客內(nèi)容在創(chuàng)作過(guò)程中,參考了大量百科知識(shí)以及其他優(yōu)秀博客,并結(jié)合筆者自身在實(shí)際工作中遇到的相關(guān)問(wèn)題。筆者希望通過(guò)這篇博客,能為各位讀者在字符編碼這一塊提供一些有價(jià)值的見(jiàn)解和幫助。 轉(zhuǎn)自https://www.cnblogs.com/codegb/p/18768600 該文章在 2025/3/14 9:53:11 編輯過(guò) |
關(guān)鍵字查詢(xún)
相關(guān)文章
正在查詢(xún)... |