作者:CPPFive
在网上爬句子的时候碰到了编码转换的问题。因为很多面向世界范围网页上普遍使用 UTF-8 编码,而本地默认使用的是 GBK(准确来说是CP936,很类似 GBK 但不完全一样)编码,并且将 UTF-8 编码直接转换成 GBK 编码有概率会爆出乱码(UTF-8 编码对应的字符集大于 GBK),所以我们需要一种能在本地处理 UTF-8 的方法。这里主要介绍一些关于计算机编码的基本知识以及在 C++ 和 Python 中处理不同编码的文件的方式。
编码
字符是以二进制形式存储在电脑中的。而为了使电脑中存储的二进制编码可以在不同的电脑上被使用,就需要通用的编码方式,即有一套类似于密码表一样的东西,使得每一个字符都可以与一个二进制数(这个二进制数通常被称为码位)相对应。ASCII 码就是非常基础的一套“密码表”,可以将常见符号以及英文字母对应成二进制数。但是ASCII码的每个字符只使用八个二进制位来存储,所以最多也只能对应 256 个字符(实际上标准 ASCII 码只使用七个二进制位,只包含 128 个字符)。而为了存储更多语言的字符,出现了更大的字符集。
Unicode
统一码(Unicode),也叫万国码、单一码,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
以上是度娘上的定义。Unicode 指的并不是某一个字符集或者编码方式,而是一项包含了若干字符集以及编码方式的编码标准。我们知道 ASCII 码的每一个字符用一个八位二进制数存储,而 Unicode 为了容纳更多字符,使用了更多字节。Unicode 中常用的字符集有 UCS-2(Universal Character Set coded in 2 octets)以及 UCS-4 ,前者使用两个字节,后者使用四个字节,这些字符集中每一个字符对应的二进制数被称为它的码位(code point)。UCS-2 共可以容纳 65536 个字符,而后来为了容纳更多文字,才发展出了 UCS-4,后者共可以容纳 1114112 个字符,人类文明所创造出的字符总量目前还远未达到这个数字。
要注意的是,UCS-2 和 UCS-4 仅仅规定了文字和它的码位之间的对应关系,并没有规定这些码位在计算机中如何存储。事实上,由于常用字符并不需要这么大的空间来存储,因此可以通过一些编码方式来节省空间,即通过某种方式将较长的码位一一对应为较短的实际编码。这些编码方式被称为 UTF(Unicode Transformation Format),其中应用较多的是 UTF-16 和 UTF-8。通常字符通过这些编码方式存储在计算机中。
GB2312、GBK、GB18030
三者都是国内常用的编码标准,其中 GB2312 和 GB18030 属于国家标准,GBK 则是二者中承上启下的部分。由于某些历史原因,GBK 至今仍然在国内被大量使用。
与 Unicode 字符相比,这几种编码标准有如下的几点不同:
- 编码不同
(废话),比如“国”这个字,在 UTF-8 中编码为 E59BBD(编码通常用十六进制数表示),在 GBK 中编码则为 B9FA。文末附了几个常用的查看字符编码的方式,有兴趣的读者可以去实践一下。 - Unicode 中每个字符的码位与计算机中实际存储的信息是不同的(因为采用了 UTF-8 等编码方式),而 GB2312、GBK、GB18030 的字符集则是与它的存储方式一致的,或者说此三者既是字符集,也是编码方式。
GB2312 只收录了简体汉字和一些常用符号和字母,共收录了 6763 个汉字;GBK 在 GB2312 的基础上扩充了一部分新增的汉字以及繁体字、日语、朝鲜语中的汉字,共收录了两万多个汉字和字符;GB18030 在 GBK 的基础上进一步扩充,收录了七万多个汉字和字符。GB18030 字符集实际上非常大,也与 Unicode 一样包含了世界上大部分字符。
之所以在已经有了 Unicode 的基础上我国还要自己开发 GB18030,原因是 GB18030 是完全面向我国的,它的编码方式更利于使用中文,例如在 GB18030 中存储大部分中文字符都只需要两个字节,而 使用 UTF-8 则需要三个字节。当然实际上由于许多主流应用软件与系统软件都是由外国开发的,不太支持 GB18030 (尤其是 Windows),因此我们平常接触更多的还是 UTF-8 或者 GBK。
不同编码的使用
Unicode 的优点是它足以容纳所有语言的字符,因此可以满足在各国之间通用的要求(想象一下假如不同国家之间使用的是各自的编码,那么每一次传输信息都将需要进行编码的转换,这是相当繁琐的)。但是通常使用中远远用不到那么多那么多字符,而 Unicode 中每个字符所占据的较大的空间此时就成为了负担。所以 GBK 这一类的只包含部分语言、占用空间也较少的字符集在各国国内仍然在大量使用。这也导致了我们前面提到的问题。
有些时候我们在访问国外网站的时候会出现乱码也是因为这种原因。网站的代码可能使用了 UTF-8 编码,但是没有在文档中进行声明,这时如果访问者的浏览器默认使用 GBK 编码,就会出现乱码。
C++中的编码问题
C++中提供了宽字节 wchar_t
以及相应的一些函数(其实就是原来的函数前面加个 w)来处理 Unicode 字符。似乎由于UTF-16 的大部分常用字符都是两个字节,而一个宽字节恰好也是两个字节,所以可以直接处理。代码如下所示:
wstring sss;
wifstream go("test.txt");
go >> sss;
wofstream back("100.txt");
back << sss;
但以上代码无法处理使用 UTF-8 编码的文件,好在 C++ 提供了相应的函数。我们可以使用 locale 函数来调整输入/输出流采用的编码方式,这样就可以处理 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 类中的一个实例,括号里的 .65001
是系统中 UTF-8 的编号。系统中编码方式被称为”活动代码页“,不同的活动代码页有不同的编号,文末附了一份常用编码方式对应代码页编号的列表。
另外,需要注意的是,如果是要输出到命令行里的话,那么输出流是会受限于命令行的编码方式的。如果一定要输出到命令行中,可以参考这篇文章来修改命令行使用的编码方式。
非常有意思的是,如果用 CP936(即 GBK)来输入输出 UTF-8 的文件,那么在有些时候不会出现乱码,另一些时候则会出现。
经过不完全测试,在输出大部分汉字以及纯 ASCII 字符的时候都不会出现问题,但是当文件中包含字符”四“时就会出现乱码(应该是一部分汉字会导致问题),并且在 ASCII 字符和中文混合使用的时候也必定会出现乱码。
Python中的字符编码问题
Python 非常的强大,这一点是毫无疑问的。在字符编码的问题上,Python 明显比 C++ 方便得多。Python3 中的字符串(即str
)统一以 Unicode 字符的形式保存。(Unicode 并不是一种编码方式。之所以说 Python3 使用 Unicode 字符保存字符串,是因为在网上查不到其具体的编码方式,只有这种笼统的介绍;经过本人测试,Python3 中一个英文字符占一个字节,一个常用中文字符占两个字节,所以应该可以排除单独使用的 UTF-8、UTF-16 或者 UTF-32 的可能;比较有可能是多种编码方式混合使用)。而在读入和输出时,Python3 会自动根据文件的类型来完成转换。以下面的代码为例:
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()
上面的程序从一个 UTF-16 编码的文件读取内容,存储到一个字符串中,再以 UTF-8 编码输出到文件中。只需要指定文件的编码方式,Python3 就会为你做好一切。如果不指定的话,Python3 会默认使用系统的编码方式。
正文就到这里结束啦,欢迎大家点赞评论支持一下(~ ̄▽ ̄)~
查看字符编码的方式
1.在线转换
2.使用 Python 查看
dest = "中" # 待查看编码的字符/字符串
dest_encode = dest.encode("utf-8") # utf-8 可以换成别的编码方式
print(dest_encode)
3.使用 Word
在 Word 里可以通过 alt+X 来将光标前的汉字/UTF-16 编码互相转换。暂时不太清楚是否可以转换成别的编码。
常用编码方式对应代码页编号的列表
代码页 国家(地区)或语言
437 美国
708 阿拉伯文(ASMO 708)
720 阿拉伯文(DOS)
850 多语言(拉丁文 I)
852 中欧(DOS) - 斯拉夫语(拉丁文 II)
855 西里尔文(俄语)
857 土耳其语
860 葡萄牙语
861 冰岛语
862 希伯来文(DOS)
863 加拿大 - 法语
865 日耳曼语
866 俄语 - 西里尔文(DOS)
869 现代希腊语
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-允许一个字节的片假名)
50222 日文(JIS-允许一个字节的片假名 - SO/SI)
50225 韩文(ISO)
50932 日文(自动选择)
50949 韩文(自动选择)
51932 日文(EUC)
51949 韩文(EUC)
52936 简体中文(HZ)
65000 Unicode (UTF-7)
65001 Unicode (UTF-8)