编码与转换 ~ Encoding & Transcoding
这次带来的是编码的问题。为什么搞这篇的原因很简单,因为我好不容易考完试,下个新的日本游戏结果标题乱码了。又想起一些人居然还会用char怼字符...罢了,这样真的容易让一个Unicode党受不了丫丫丫丫。 故,想浅浅地介绍一下文字编码的问题,或者说,字符集编码的问题,并且拿C++和C#语言作为例子,看如何解决这个问题。 Encoding? 字符串的编码,离我们很近,比如你现在正在体验它——虽然不是那么直接。中文被编码为utf-8,通过http协议传到设备上,再映射为文字,显示在屏幕上,让人可以阅读。这个过程已经足矣了解啥是字符串编码了。如果我们幻想地认为计算机显示的文字只是一张张的图片,那么上面的过程其实就是: 可以直观的感受到,奇妙的数字经过一个特殊的映射器(中间的画图3D黄色的框)得到图片linux编码,从而显示文字。当然,终归到底,显示这些图片是个很大的问题,因此我们只能幻想它是个很轻松的过程——总而言之那不是本文的重点,要是哪天被文字显示恶心了再写咋显示它们的文章。 而这个映射器需要一个规则来指引。这个规则就是编码起的作用。 就这么简单,一个规范,告诉这个画图3D黄的框咋办。 美国人搞了一套早期的,叫做ASCII,或者也就是大学or高中课堂上的阿斯克i码。其作用也是如此。 Local Code Page? 这咱得讲故事了。这个故事,本来在自由国好好的,但是计算机会普及开。自由国的文字组成很简单。or英文的文字组成很简单,手脚并用,26个字母,加点数字标点,没了。 计算机来到了东方。 那里的个国家——他们的文字...感受下? 古老的东方国家的文字 很快就会发现,这些国家的字符早就不是256个能搞定的事情了——他们过万的文字需要一个超大的字符集。 直觉的方案——ASCII不是只用到了127嘛,那么我们从128(0x80)开始,再搞一套不就行了。这样做,既保证了兼容古老的ASCII,又保证了支持这些地区的超大字符集。因为通常使用大于1个字节来拓展,因此它们通常又被叫做多字节字符集。而本机的字符集,被叫做本地代码页。 于是乎,各个国家都这样干。甚至一些调皮的省份也这样干: ——GB2312、GB18030 ——Big5 ——Shift-JIS ——Windows 1252... 要命的是: 各个的编码规则不同,它们却使用了同样的一段区域。这就导致了问题: 张三在.....在村子里的文本是GB2312。 现在张三要去找日本人,日本的编码是Shift-JIS,结果张三的文本乱码了(例如文章开始的那张图)。 张三很无语,因此还毁了合同。 这,就是这种方式带来的问题——使用了一段重叠的区域拓展,导致跨地区、跨计算机很成问题,跨了编码不对,乱码找上门,自带加密。 Unicode? 很早就有组织尝试解决此问题。是两个不同的组织——不过,他们似乎达成共识。大约30年前,一起搞出了一个叫做Unicode的玩意,用于表示各个地区的编码。 Unicode是一套标准,它囊括了主要国家和地区的文字,可以容纳百万级别的字符,但是至今也没用满。这些玩意被放在了不同的位置。如此一来,即可解决重叠带来的乱码问题。 Unicode有很多实现:UTF-8、UTF-16等等。UTF-8是特殊的变长字符集,而UTF-16是定长的2字节长的字符集。 当然,UTF-16是不够的,很显然,它只能容纳几万个字符。但是对于主流情况来说,大概是够了。 而对于我(or我们,至少对于我个人而言是如此)来说,只需要选择一种合适的实现(例如UTF-16),然后用上支持的API,即可轻松解决跨地域乱码的问题。高级点,更好的解决国际化问题。 LCP or Unicode? 各个厂家当然在这方面做了努力,比如巨硬提供的API有两个版本——一个是使用多字节编码的xxxA版,一个是xxxW版,使用UTF-16。 当然我乐意选择后者。 不过很遗憾,很多地方,并没有Unicode的支持,你需要退而求其次使用UTF-8,或者是老的gb2312这类的多字节字符集编码。具体,自己取舍。 UTF-16 on C++ & C# 很棒的是C#的string就是Unicode的,在C#中,大可不用担心编码问题。 C++就没这么幸运了(因为我一般在Windows下泡VS,因此下面的内容大概或许对Linux、MacOS等不想使用MS VC++编译器的同志不友好,如果有那种情况的话见谅)。 一般来说,大学课堂会告诉你这样使用字符串:
一般来说,这个字符串的编码和你的本机代码页有关。 如果需要使用UTF-16,你需要wchar_t类型:
为了方便,可以引入tchar.h这个头文件:
tchar会按照你的 编译器预处理器定义来更改是char类型还是wchar_t类型。正常情况下,一个习惯char的人会先被tchar恶心一通,才能适应Unicode的快乐。 当然,有表示utf-8的方法,可以具体查找(类似的,某个前缀,什么u8"之类的"),这里不再展开。 Read & Write in C++ 我们解决了第一个问题——在编程语言里面表示Unicode字符串。 第二个问题——识别编码。 一个现实的答案是,识别UTF-16、UTF-32是轻而易举的(感兴趣可以查一下,这些编码在最前面的几个字节有着奇妙的规律),但是识别它的编码是GB2312还是big5,却没那么容易。 因此,我们要求助于一个圆形的轮子——uchardet: clone下来,打开你的C++秃顶器,看一下它的示例应该就会了。还不会?哈~~~ 简而言之,思路超easy: uchardet_new创建handleuchardet_handle_data送入数据uchardet_data_end表示数据到此结束uchardet_get_charset取得字符串编码的名称。进行对应处理。uchardet_delete释放资源。 如果你正确操作,应该可以拿到uchardet_get_charset的返回值(自己去看例子程序。。我才不想丢代码给Ctrl+C大佬),它是你送入的字符串的编码名称。接下来,我们有两条路——把当前程序的代码页调对,或者转换成UTF-16使用。我选择后者。 转换成UTF-16怎么办呢?这得用native api了:MultiByteToWideChar。前往巨硬官网学习该API: 简而言之,我们这样操作: MultiByteToWideChar(代码页id, 0, 原始字符串, 原始字符串长度, NULL, 0);拿到转换后的字符串长度分配内存MultiByteToWideChar(代码页id, 0, 原始字符串, 原始字符串长度, 存放结果的位置, 结果的缓冲区长度);转换编码 也是很简单的。现在的关键问题是代码页id在哪。你需要去这里: 这下明白了,我们拿uchardet取得编码的名称,然后按照这个表,得到代码页id。这个表很长,照着打很累,因此:
需要注意的是,你永远没法猜出"char string"是什么编码的——因为几乎所有的编码都兼容ASCII,所以它是啥编码都行。这个故事告诉我们,文件编码判断不是确定的,判断出来是ASCII,但是原本或许是GB2312。其余同理。 好,最后是文件读写。准确说,这里只说读取。因为写入我会统一Unicode。但是,对于一个好的编辑器,你不应该更改源文件的编码。如果确实如此,思路是和下面反过来的,用Wchar转多字符集的api即可。 我们必须读取一整个文件,不能读一点转码一点——因为无法预知这一段是否是完整的(考虑需要37个字节编码,但是你第一次读了36个。。。那就gg了)。因此思路很清晰: fopen,该咋办咋办ftell,取得文件长度(注意流读取位置,很显然你需要fseek到末尾才能ftell出大小。然后为了read还得rewind回去)。fread,读取文件uchardet判断编码暴力表得到代码页转码UTF-16完工 这就是处理它的方式。写入自然反过来,我们需要写入转换后的多字节字符集编码的数据。 Read & Write in C# C#的处理方式就爽多了。 打开你的nuget包管理器,找到一个叫做UTF.Unknown的库。下载下来,它就是uchardet在C#上的移植。 使用起来更简单:
没了。记得探索一下encode的属性。这样就完事了。 然后,File.ReadAllText、StreamWriter等等都有指定编码的方式,需要注意的是,编码是System.Text.Encoding,它是DetectionResult里面一个特定元素的属性,并不是DetectionResult。 以上。 (编辑:92站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |