作者: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)不同:
- 編碼不同
(廢話),比如“國(guó)”這個(gè)字,在 UTF-8 中編碼為 E59BBD(編碼通常用十六進(jìn)制數(shù)表示),在 GBK 中編碼則為 B9FA。文末附了幾個(gè)常用的查看字符編碼的方式,有興趣的讀者可以去實(shí)踐一下。 - 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)。




經(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)換
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)