作者:CPPFive
在網(wǎng)上爬句子的時(shí)候碰到了編碼轉(zhuǎn)換的問題飒焦。因?yàn)楹芏嗝嫦蚴澜绶秶W(wǎng)頁上普遍使用 UTF-8 編碼那槽,而本地默認(rèn)使用的是 GBK(準(zhǔn)確來說是CP936悼沿,很類似 GBK 但不完全一樣)編碼,并且將 UTF-8 編碼直接轉(zhuǎn)換成 GBK 編碼有概率會爆出亂碼(UTF-8 編碼對應(yīng)的字符集大于 GBK)骚灸,所以我們需要一種能在本地處理 UTF-8 的方法糟趾。這里主要介紹一些關(guān)于計(jì)算機(jī)編碼的基本知識以及在 C++ 和 Python 中處理不同編碼的文件的方式。
編碼
字符是以二進(jìn)制形式存儲在電腦中的甚牲。而為了使電腦中存儲的二進(jìn)制編碼可以在不同的電腦上被使用义郑,就需要通用的編碼方式,即有一套類似于密碼表一樣的東西丈钙,使得每一個(gè)字符都可以與一個(gè)二進(jìn)制數(shù)(這個(gè)二進(jìn)制數(shù)通常被稱為碼位)相對應(yīng)非驮。ASCII 碼就是非常基礎(chǔ)的一套“密碼表”雏赦,可以將常見符號以及英文字母對應(yīng)成二進(jìn)制數(shù)劫笙。但是ASCII碼的每個(gè)字符只使用八個(gè)二進(jìn)制位來存儲,所以最多也只能對應(yīng) 256 個(gè)字符(實(shí)際上標(biāo)準(zhǔn) ASCII 碼只使用七個(gè)二進(jìn)制位星岗,只包含 128 個(gè)字符)填大。而為了存儲更多語言的字符,出現(xiàn)了更大的字符集俏橘。
Unicode
統(tǒng)一碼(Unicode)允华,也叫萬國碼、單一碼寥掐,是計(jì)算機(jī)科學(xué)領(lǐng)域里的一項(xiàng)業(yè)界標(biāo)準(zhǔn)例获,包括字符集、編碼方案等曹仗。Unicode 是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的榨汤,它為每種語言中的每個(gè)字符設(shè)定了統(tǒng)一并且唯一的二進(jìn)制編碼,以滿足跨語言怎茫、跨平臺進(jìn)行文本轉(zhuǎn)換收壕、處理的要求。
以上是度娘上的定義轨蛤。Unicode 指的并不是某一個(gè)字符集或者編碼方式蜜宪,而是一項(xiàng)包含了若干字符集以及編碼方式的編碼標(biāo)準(zhǔn)。我們知道 ASCII 碼的每一個(gè)字符用一個(gè)八位二進(jìn)制數(shù)存儲祥山,而 Unicode 為了容納更多字符圃验,使用了更多字節(jié)。Unicode 中常用的字符集有 UCS-2(Universal Character Set coded in 2 octets)以及 UCS-4 缝呕,前者使用兩個(gè)字節(jié)澳窑,后者使用四個(gè)字節(jié)斧散,這些字符集中每一個(gè)字符對應(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ī)定了文字和它的碼位之間的對應(yīng)關(guān)系煎源,并沒有規(guī)定這些碼位在計(jì)算機(jī)中如何存儲色迂。事實(shí)上,由于常用字符并不需要這么大的空間來存儲手销,因此可以通過一些編碼方式來節(jié)省空間脚草,即通過某種方式將較長的碼位一一對應(yīng)為較短的實(shí)際編碼。這些編碼方式被稱為 UTF(Unicode Transformation Format)原献,其中應(yīng)用較多的是 UTF-16 和 UTF-8馏慨。通常字符通過這些編碼方式存儲在計(jì)算機(jī)中。
GB2312姑隅、GBK写隶、GB18030
三者都是國內(nèi)常用的編碼標(biāo)準(zhǔn),其中 GB2312 和 GB18030 屬于國家標(biāo)準(zhǔn)讲仰,GBK 則是二者中承上啟下的部分慕趴。由于某些歷史原因,GBK 至今仍然在國內(nèi)被大量使用鄙陡。
與 Unicode 字符相比冕房,這幾種編碼標(biāo)準(zhǔn)有如下的幾點(diǎn)不同:
- 編碼不同
(廢話),比如“國”這個(gè)字趁矾,在 UTF-8 中編碼為 E59BBD(編碼通常用十六進(jìn)制數(shù)表示)耙册,在 GBK 中編碼則為 B9FA。文末附了幾個(gè)常用的查看字符編碼的方式毫捣,有興趣的讀者可以去實(shí)踐一下详拙。 - Unicode 中每個(gè)字符的碼位與計(jì)算機(jī)中實(shí)際存儲的信息是不同的(因?yàn)椴捎昧?UTF-8 等編碼方式),而 GB2312蔓同、GBK饶辙、GB18030 的字符集則是與它的存儲方式一致的,或者說此三者既是字符集斑粱,也是編碼方式弃揽。
GB2312 只收錄了簡體漢字和一些常用符號和字母,共收錄了 6763 個(gè)漢字;GBK 在 GB2312 的基礎(chǔ)上擴(kuò)充了一部分新增的漢字以及繁體字矿微、日語痕慢、朝鮮語中的漢字,共收錄了兩萬多個(gè)漢字和字符冷冗;GB18030 在 GBK 的基礎(chǔ)上進(jìn)一步擴(kuò)充,收錄了七萬多個(gè)漢字和字符惑艇。GB18030 字符集實(shí)際上非常大蒿辙,也與 Unicode 一樣包含了世界上大部分字符。
之所以在已經(jīng)有了 Unicode 的基礎(chǔ)上我國還要自己開發(fā) GB18030滨巴,原因是 GB18030 是完全面向我國的思灌,它的編碼方式更利于使用中文,例如在 GB18030 中存儲大部分中文字符都只需要兩個(gè)字節(jié)恭取,而 使用 UTF-8 則需要三個(gè)字節(jié)泰偿。當(dāng)然實(shí)際上由于許多主流應(yīng)用軟件與系統(tǒng)軟件都是由外國開發(fā)的,不太支持 GB18030 (尤其是 Windows)蜈垮,因此我們平常接觸更多的還是 UTF-8 或者 GBK耗跛。
不同編碼的使用
Unicode 的優(yōu)點(diǎn)是它足以容納所有語言的字符,因此可以滿足在各國之間通用的要求(想象一下假如不同國家之間使用的是各自的編碼攒发,那么每一次傳輸信息都將需要進(jìn)行編碼的轉(zhuǎn)換调塌,這是相當(dāng)繁瑣的)。但是通常使用中遠(yuǎn)遠(yuǎn)用不到那么多那么多字符惠猿,而 Unicode 中每個(gè)字符所占據(jù)的較大的空間此時(shí)就成為了負(fù)擔(dān)羔砾。所以 GBK 這一類的只包含部分語言、占用空間也較少的字符集在各國國內(nèi)仍然在大量使用偶妖。這也導(dǎo)致了我們前面提到的問題姜凄。
有些時(shí)候我們在訪問國外網(wǎng)站的時(shí)候會出現(xiàn)亂碼也是因?yàn)檫@種原因。網(wǎng)站的代碼可能使用了 UTF-8 編碼趾访,但是沒有在文檔中進(jìn)行聲明态秧,這時(shí)如果訪問者的瀏覽器默認(rèn)使用 GBK 編碼,就會出現(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í)例,括號里的 .65001
是系統(tǒng)中 UTF-8 的編號脆粥。系統(tǒng)中編碼方式被稱為”活動代碼頁“砌溺,不同的活動代碼頁有不同的編號,文末附了一份常用編碼方式對應(yīng)代碼頁編號的列表变隔。
另外规伐,需要注意的是,如果是要輸出到命令行里的話匣缘,那么輸出流是會受限于命令行的編碼方式的猖闪。如果一定要輸出到命令行中,可以參考這篇文章來修改命令行使用的編碼方式肌厨。
非常有意思的是培慌,如果用 CP936(即 GBK)來輸入輸出 UTF-8 的文件,那么在有些時(shí)候不會出現(xiàn)亂碼柑爸,另一些時(shí)候則會出現(xiàn)吵护。
經(jīng)過不完全測試,在輸出大部分漢字以及純 ASCII 字符的時(shí)候都不會出現(xiàn)問題表鳍,但是當(dāng)文件中包含字符”四“時(shí)就會出現(xiàn)亂碼(應(yīng)該是一部分漢字會導(dǎo)致問題)馅而,并且在 ASCII 字符和中文混合使用的時(shí)候也必定會出現(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)過本人測試就缆,Python3 中一個(gè)英文字符占一個(gè)字節(jié)帖渠,一個(gè)常用中文字符占兩個(gè)字節(jié),所以應(yīng)該可以排除單獨(dú)使用的 UTF-8竭宰、UTF-16 或者 UTF-32 的可能空郊;比較有可能是多種編碼方式混合使用)。而在讀入和輸出時(shí)切揭,Python3 會自動根據(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)容,存儲到一個(gè)字符串中廓旬,再以 UTF-8 編碼輸出到文件中哼审。只需要指定文件的編碼方式,Python3 就會為你做好一切。如果不指定的話涩盾,Python3 會默認(rèn)使用系統(tǒng)的編碼方式十气。
正文就到這里結(jié)束啦,歡迎大家點(diǎn)贊評論支持一下(~ ̄▽ ̄)~
查看字符編碼的方式
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)換成別的編碼砸西。
常用編碼方式對應(yīng)代碼頁編號的列表
代碼頁 國家(地區(qū))或語言
437 美國
708 阿拉伯文(ASMO 708)
720 阿拉伯文(DOS)
850 多語言(拉丁文 I)
852 中歐(DOS) - 斯拉夫語(拉丁文 II)
855 西里爾文(俄語)
857 土耳其語
860 葡萄牙語
861 冰島語
862 希伯來文(DOS)
863 加拿大 - 法語
865 日耳曼語
866 俄語 - 西里爾文(DOS)
869 現(xiàn)代希臘語
874 泰文(Windows)
932 日文(Shift-JIS)
936 中國 - 簡體中文(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 自動選擇
50220 日文(JIS)
50221 日文(JIS-允許一個(gè)字節(jié)的片假名)
50222 日文(JIS-允許一個(gè)字節(jié)的片假名 - SO/SI)
50225 韓文(ISO)
50932 日文(自動選擇)
50949 韓文(自動選擇)
51932 日文(EUC)
51949 韓文(EUC)
52936 簡體中文(HZ)
65000 Unicode (UTF-7)
65001 Unicode (UTF-8)