字符編碼及其在 C++ 與 Python 中的使用

作者:CPPFive

在網(wǎng)上爬句子的時(shí)候碰到了編碼轉(zhuǎn)換的問題。因?yàn)楹芏嗝嫦蚴澜绶秶W(wǎng)頁(yè)上普遍使用 UTF-8 編碼,而本地默認(rèn)使用的是 GBK(準(zhǔn)確來說是CP936,很類似 GBK 但不完全一樣)編碼,并且將 UTF-8 編碼直接轉(zhuǎn)換成 GBK 編碼有概率會(huì)爆出亂碼(UTF-8 編碼對(duì)應(yīng)的字符集大于 GBK),所以我們需要一種能在本地處理 UTF-8 的方法。這里主要介紹一些關(guān)于計(jì)算機(jī)編碼的基本知識(shí)以及在 C++ 和 Python 中處理不同編碼的文件的方式。

編碼

字符是以二進(jìn)制形式存儲(chǔ)在電腦中的。而為了使電腦中存儲(chǔ)的二進(jìn)制編碼可以在不同的電腦上被使用,就需要通用的編碼方式,即有一套類似于密碼表一樣的東西,使得每一個(gè)字符都可以與一個(gè)二進(jìn)制數(shù)(這個(gè)二進(jìn)制數(shù)通常被稱為碼位)相對(duì)應(yīng)。ASCII 碼就是非?;A(chǔ)的一套“密碼表”,可以將常見符號(hào)以及英文字母對(duì)應(yīng)成二進(jìn)制數(shù)。但是ASCII碼的每個(gè)字符只使用八個(gè)二進(jìn)制位來存儲(chǔ),所以最多也只能對(duì)應(yīng) 256 個(gè)字符(實(shí)際上標(biāo)準(zhǔn) ASCII 碼只使用七個(gè)二進(jìn)制位,只包含 128 個(gè)字符)。而為了存儲(chǔ)更多語(yǔ)言的字符,出現(xiàn)了更大的字符集。

Unicode

統(tǒng)一碼(Unicode),也叫萬國(guó)碼、單一碼,是計(jì)算機(jī)科學(xué)領(lǐng)域里的一項(xiàng)業(yè)界標(biāo)準(zhǔn),包括字符集、編碼方案等。Unicode 是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的,它為每種語(yǔ)言中的每個(gè)字符設(shè)定了統(tǒng)一并且唯一的二進(jìn)制編碼,以滿足跨語(yǔ)言、跨平臺(tái)進(jìn)行文本轉(zhuǎn)換、處理的要求。

以上是度娘上的定義。Unicode 指的并不是某一個(gè)字符集或者編碼方式,而是一項(xiàng)包含了若干字符集以及編碼方式的編碼標(biāo)準(zhǔn)。我們知道 ASCII 碼的每一個(gè)字符用一個(gè)八位二進(jìn)制數(shù)存儲(chǔ),而 Unicode 為了容納更多字符,使用了更多字節(jié)。Unicode 中常用的字符集有 UCS-2(Universal Character Set coded in 2 octets)以及 UCS-4 ,前者使用兩個(gè)字節(jié),后者使用四個(gè)字節(jié),這些字符集中每一個(gè)字符對(duì)應(yīng)的二進(jìn)制數(shù)被稱為它的碼位(code point)。UCS-2 共可以容納 65536 個(gè)字符,而后來為了容納更多文字,才發(fā)展出了 UCS-4,后者共可以容納 1114112 個(gè)字符,人類文明所創(chuàng)造出的字符總量目前還遠(yuǎn)未達(dá)到這個(gè)數(shù)字。

要注意的是,UCS-2 和 UCS-4 僅僅規(guī)定了文字和它的碼位之間的對(duì)應(yīng)關(guān)系,并沒有規(guī)定這些碼位在計(jì)算機(jī)中如何存儲(chǔ)。事實(shí)上,由于常用字符并不需要這么大的空間來存儲(chǔ),因此可以通過一些編碼方式來節(jié)省空間,即通過某種方式將較長(zhǎng)的碼位一一對(duì)應(yīng)為較短的實(shí)際編碼。這些編碼方式被稱為 UTF(Unicode Transformation Format),其中應(yīng)用較多的是 UTF-16 和 UTF-8。通常字符通過這些編碼方式存儲(chǔ)在計(jì)算機(jī)中。

GB2312、GBK、GB18030

三者都是國(guó)內(nèi)常用的編碼標(biāo)準(zhǔn),其中 GB2312 和 GB18030 屬于國(guó)家標(biāo)準(zhǔn),GBK 則是二者中承上啟下的部分。由于某些歷史原因,GBK 至今仍然在國(guó)內(nèi)被大量使用。

與 Unicode 字符相比,這幾種編碼標(biāo)準(zhǔn)有如下的幾點(diǎn)不同:

  1. 編碼不同(廢話),比如“國(guó)”這個(gè)字,在 UTF-8 中編碼為 E59BBD(編碼通常用十六進(jìn)制數(shù)表示),在 GBK 中編碼則為 B9FA。文末附了幾個(gè)常用的查看字符編碼的方式,有興趣的讀者可以去實(shí)踐一下。
  2. Unicode 中每個(gè)字符的碼位與計(jì)算機(jī)中實(shí)際存儲(chǔ)的信息是不同的(因?yàn)椴捎昧?UTF-8 等編碼方式),而 GB2312、GBK、GB18030 的字符集則是與它的存儲(chǔ)方式一致的,或者說此三者既是字符集,也是編碼方式。

GB2312 只收錄了簡(jiǎn)體漢字和一些常用符號(hào)和字母,共收錄了 6763 個(gè)漢字;GBK 在 GB2312 的基礎(chǔ)上擴(kuò)充了一部分新增的漢字以及繁體字、日語(yǔ)、朝鮮語(yǔ)中的漢字,共收錄了兩萬多個(gè)漢字和字符;GB18030 在 GBK 的基礎(chǔ)上進(jìn)一步擴(kuò)充,收錄了七萬多個(gè)漢字和字符。GB18030 字符集實(shí)際上非常大,也與 Unicode 一樣包含了世界上大部分字符。

之所以在已經(jīng)有了 Unicode 的基礎(chǔ)上我國(guó)還要自己開發(fā) GB18030,原因是 GB18030 是完全面向我國(guó)的,它的編碼方式更利于使用中文,例如在 GB18030 中存儲(chǔ)大部分中文字符都只需要兩個(gè)字節(jié),而 使用 UTF-8 則需要三個(gè)字節(jié)。當(dāng)然實(shí)際上由于許多主流應(yīng)用軟件與系統(tǒng)軟件都是由外國(guó)開發(fā)的,不太支持 GB18030 (尤其是 Windows),因此我們平常接觸更多的還是 UTF-8 或者 GBK。

不同編碼的使用

Unicode 的優(yōu)點(diǎn)是它足以容納所有語(yǔ)言的字符,因此可以滿足在各國(guó)之間通用的要求(想象一下假如不同國(guó)家之間使用的是各自的編碼,那么每一次傳輸信息都將需要進(jìn)行編碼的轉(zhuǎn)換,這是相當(dāng)繁瑣的)。但是通常使用中遠(yuǎn)遠(yuǎn)用不到那么多那么多字符,而 Unicode 中每個(gè)字符所占據(jù)的較大的空間此時(shí)就成為了負(fù)擔(dān)。所以 GBK 這一類的只包含部分語(yǔ)言、占用空間也較少的字符集在各國(guó)國(guó)內(nèi)仍然在大量使用。這也導(dǎo)致了我們前面提到的問題。

有些時(shí)候我們?cè)谠L問國(guó)外網(wǎng)站的時(shí)候會(huì)出現(xiàn)亂碼也是因?yàn)檫@種原因。網(wǎng)站的代碼可能使用了 UTF-8 編碼,但是沒有在文檔中進(jìn)行聲明,這時(shí)如果訪問者的瀏覽器默認(rèn)使用 GBK 編碼,就會(huì)出現(xiàn)亂碼。

C++中的編碼問題

C++中提供了寬字節(jié) wchar_t 以及相應(yīng)的一些函數(shù)(其實(shí)就是原來的函數(shù)前面加個(gè) w)來處理 Unicode 字符。似乎由于UTF-16 的大部分常用字符都是兩個(gè)字節(jié),而一個(gè)寬字節(jié)恰好也是兩個(gè)字節(jié),所以可以直接處理。代碼如下所示:

wstring sss;
wifstream go("test.txt");
go >> sss;
wofstream back("100.txt");
back << sss;

但以上代碼無法處理使用 UTF-8 編碼的文件,好在 C++ 提供了相應(yīng)的函數(shù)。我們可以使用 locale 函數(shù)來調(diào)整輸入/輸出流采用的編碼方式,這樣就可以處理 UTF-8 編碼的文件了。代碼如下:

wstring sss;
locale china(".65001");
wifstream go("test.txt");
go.imbue(china);
go >> sss;
wofstream back("100.txt");
back.imbue(china);
back << sss;

上面的 china 是locale 類中的一個(gè)實(shí)例,括號(hào)里的 .65001 是系統(tǒng)中 UTF-8 的編號(hào)。系統(tǒng)中編碼方式被稱為”活動(dòng)代碼頁(yè)“,不同的活動(dòng)代碼頁(yè)有不同的編號(hào),文末附了一份常用編碼方式對(duì)應(yīng)代碼頁(yè)編號(hào)的列表。

另外,需要注意的是,如果是要輸出到命令行里的話,那么輸出流是會(huì)受限于命令行的編碼方式的。如果一定要輸出到命令行中,可以參考這篇文章來修改命令行使用的編碼方式。

非常有意思的是,如果用 CP936(即 GBK)來輸入輸出 UTF-8 的文件,那么在有些時(shí)候不會(huì)出現(xiàn)亂碼,另一些時(shí)候則會(huì)出現(xiàn)。

源文件

目標(biāo)文件中可以正常顯示

源文件

目標(biāo)文件出現(xiàn)亂碼

經(jīng)過不完全測(cè)試,在輸出大部分漢字以及純 ASCII 字符的時(shí)候都不會(huì)出現(xiàn)問題,但是當(dāng)文件中包含字符”四“時(shí)就會(huì)出現(xiàn)亂碼(應(yīng)該是一部分漢字會(huì)導(dǎo)致問題),并且在 ASCII 字符和中文混合使用的時(shí)候也必定會(huì)出現(xiàn)亂碼。

Python中的字符編碼問題

Python 非常的強(qiáng)大,這一點(diǎn)是毫無疑問的。在字符編碼的問題上,Python 明顯比 C++ 方便得多。Python3 中的字符串(即str)統(tǒng)一以 Unicode 字符的形式保存。(Unicode 并不是一種編碼方式。之所以說 Python3 使用 Unicode 字符保存字符串,是因?yàn)樵诰W(wǎng)上查不到其具體的編碼方式,只有這種籠統(tǒng)的介紹;經(jīng)過本人測(cè)試,Python3 中一個(gè)英文字符占一個(gè)字節(jié),一個(gè)常用中文字符占兩個(gè)字節(jié),所以應(yīng)該可以排除單獨(dú)使用的 UTF-8、UTF-16 或者 UTF-32 的可能;比較有可能是多種編碼方式混合使用)。而在讀入和輸出時(shí),Python3 會(huì)自動(dòng)根據(jù)文件的類型來完成轉(zhuǎn)換。以下面的代碼為例:

with open("test.txt","r",encoding='utf-16') as f,open("100.txt","w",encoding='utf-8') as p:
    sss=f.readline()
    p.write(sss)
    f.close()
    p.close()

上面的程序從一個(gè) UTF-16 編碼的文件讀取內(nèi)容,存儲(chǔ)到一個(gè)字符串中,再以 UTF-8 編碼輸出到文件中。只需要指定文件的編碼方式,Python3 就會(huì)為你做好一切。如果不指定的話,Python3 會(huì)默認(rèn)使用系統(tǒng)的編碼方式。

正文就到這里結(jié)束啦,歡迎大家點(diǎn)贊評(píng)論支持一下(~ ̄▽ ̄)~

查看字符編碼的方式

1.在線轉(zhuǎn)換

點(diǎn)這里

2.使用 Python 查看

dest = "中"      # 待查看編碼的字符/字符串 
dest_encode = dest.encode("utf-8")      # utf-8 可以換成別的編碼方式
print(dest_encode)

3.使用 Word

在 Word 里可以通過 alt+X 來將光標(biāo)前的漢字/UTF-16 編碼互相轉(zhuǎn)換。暫時(shí)不太清楚是否可以轉(zhuǎn)換成別的編碼。

常用編碼方式對(duì)應(yīng)代碼頁(yè)編號(hào)的列表

代碼頁(yè)       國(guó)家(地區(qū))或語(yǔ)言 
437          美國(guó) 
708          阿拉伯文(ASMO 708)
720          阿拉伯文(DOS)
850          多語(yǔ)言(拉丁文 I) 
852          中歐(DOS) - 斯拉夫語(yǔ)(拉丁文 II) 
855          西里爾文(俄語(yǔ)) 
857          土耳其語(yǔ) 
860          葡萄牙語(yǔ) 
861          冰島語(yǔ) 
862          希伯來文(DOS)
863          加拿大 - 法語(yǔ) 
865          日耳曼語(yǔ) 
866          俄語(yǔ) - 西里爾文(DOS) 
869          現(xiàn)代希臘語(yǔ)
874          泰文(Windows)
932          日文(Shift-JIS)
936          中國(guó) - 簡(jiǎn)體中文(GB2312)
949          韓文
950          繁體中文(Big5)
1200         Unicode        
1201         Unicode (Big-Endian)
1250         中歐(Windows)
1251         西里爾文(Windows)
1252         西歐(Windows)
1253         希臘文(Windows)
1254         土耳其文(Windows)
1255         希伯來文(Windows)
1256         阿拉伯文(Windows)
1257         波羅的海文(Windows)
1258         越南文(Windows)
20866        西里爾文(KOI8-R)
21866        西里爾文(KOI8-U)
28592        中歐(ISO)
28593        拉丁文 3 (ISO)
28594        波羅的海文(ISO)
28595        西里爾文(ISO)
28596        阿拉伯文(ISO)
28597        希臘文(ISO)
28598        希伯來文(ISO-Visual)
38598        希伯來文(ISO-Logical)
50000        用戶定義的
50001        自動(dòng)選擇
50220        日文(JIS)
50221        日文(JIS-允許一個(gè)字節(jié)的片假名)
50222        日文(JIS-允許一個(gè)字節(jié)的片假名 - SO/SI)
50225        韓文(ISO)
50932        日文(自動(dòng)選擇)
50949        韓文(自動(dòng)選擇)
51932        日文(EUC)
51949        韓文(EUC)
52936        簡(jiǎn)體中文(HZ)
65000        Unicode (UTF-7)
65001        Unicode (UTF-8)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容