加入收藏 | 设为首页 | 会员中心 | 我要投稿 天瑞地安资讯网 (https://www.52baoding.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

编码与转换 ~ Encoding & Transcoding

发布时间:2022-12-21 13:07:38 所属栏目:Linux 来源:未知
导读: 这次带来的是编码的问题。为什么搞这篇的原因很简单,因为我好不容易考完试,下个新的日本游戏结果标题乱码了。又想起一些人居然还会用char怼字符...罢了,这样真的容易让一个Unicode党受不

这次带来的是编码的问题。为什么搞这篇的原因很简单,因为我好不容易考完试,下个新的日本游戏结果标题乱码了。又想起一些人居然还会用char怼字符...罢了,这样真的容易让一个Unicode党受不了丫丫丫丫。

故,想浅浅地介绍一下文字编码的问题,或者说,字符集编码的问题,并且拿C++和C#语言作为例子,看如何解决这个问题。

Encoding?

字符串的编码,离我们很近,比如你现在正在体验它——虽然不是那么直接。中文被编码为utf-8,通过http协议传到设备上,再映射为文字,显示在屏幕上,让人可以阅读。这个过程已经足矣了解啥是字符串编码了。如果我们幻想地认为计算机显示的文字只是一张张的图片,那么上面的过程其实就是:

linux 文件utf 8编码_linux编码_linux 修改系统编码

可以直观的感受到,奇妙的数字经过一个特殊的映射器(中间的画图3D黄色的框)得到图片linux编码,从而显示文字。当然,终归到底,显示这些图片是个很大的问题,因此我们只能幻想它是个很轻松的过程——总而言之那不是本文的重点,要是哪天被文字显示恶心了再写咋显示它们的文章。

而这个映射器需要一个规则来指引。这个规则就是编码起的作用。

就这么简单,一个规范,告诉这个画图3D黄的框咋办。

美国人搞了一套早期的,叫做ASCII,或者也就是大学or高中课堂上的阿斯克i码。其作用也是如此。

Local Code Page?

这咱得讲故事了。这个故事,本来在自由国好好的,但是计算机会普及开。自由国的文字组成很简单。or英文的文字组成很简单,手脚并用,26个字母,加点数字标点,没了。

计算机来到了东方。

那里的个国家——他们的文字...感受下?

linux编码_linux 文件utf 8编码_linux 修改系统编码

古老的东方国家的文字

很快就会发现,这些国家的字符早就不是256个能搞定的事情了——他们过万的文字需要一个超大的字符集。

直觉的方案——ASCII不是只用到了127嘛,那么我们从128(0x80)开始,再搞一套不就行了。这样做,既保证了兼容古老的ASCII,又保证了支持这些地区的超大字符集。因为通常使用大于1个字节来拓展,因此它们通常又被叫做多字节字符集。而本机的字符集,被叫做本地代码页。

于是乎,各个国家都这样干。甚至一些调皮的省份也这样干:

——GB2312、GB18030

——Big5

——Shift-JIS

——Windows 1252...

要命的是:

linux编码_linux 修改系统编码_linux 文件utf 8编码

各个的编码规则不同,它们却使用了同样的一段区域。这就导致了问题:

张三在.....在村子里的文本是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++编译器的同志不友好,如果有那种情况的话见谅)。

一般来说,大学课堂会告诉你这样使用字符串:

std::string path = "This is a ああ /// 字符串";

一般来说,这个字符串的编码和你的本机代码页有关。

如果需要使用UTF-16,你需要wchar_t类型:

wchar_t uft_16_char = L'A';

为了方便,可以引入tchar.h这个头文件:

#include 
// .....
TCHAR ch = _T('A');

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。这个表很长,照着打很累,因此:

// define
#define  MAP_ITEM(str, name, id)        { str, CodePage(id, CharEncoding:: ## name) } 
// struct
    struct CodePage
    {
        int locid;                                      // 代码页id(只有多字节编码会有这个值)
        CharEncoding encoding;                          // 编码方式
        CodePage() = default;
        CodePage(int _id, CharEncoding _enc) noexcept
        {
            locid = _id;
            encoding = _enc;
        }
    };
// enum
    enum class CharEncoding                         // 支持的字符编码
    {
        UTF16_LE,     UTF16_BE,     UTF32_LE,     UTF32_BE, 
        UTF8_BOM,     UTF_8,        ASCII,
        ISO_8859_1,   ISO_8859_2,   ISO_8859_3,   ISO_8859_4,
        ISO_8859_5,   ISO_8859_6,   ISO_8859_7,   ISO_8859_8,
        ISO_8859_9,   ISO_8859_13,  ISO_8859_15,
        WINDOWS_1250, WINDOWS_1251, WINDOWS_1252, WINDOWS_1253, 
        WINDOWS_1254, WINDOWS_1255, WINDOWS_1256, WINDOWS_1257, 
        WINDOWS_1258,
        EUC_JP,       BIG5,         IBM855,       GB2312, 
        ISO_2022_JP,  KOI8_R,       SHIFT_JIS,    GB18030,
        MAC_CYRILLIC,
        UNKNOWN
    };
// table
        static const std::map<std::string, CodePage> codePage =
        {                                           // 受支持的编码表
            MAP_ITEM("BIG5", BIG5, 950),
            MAP_ITEM("IBM855", IBM855, 855),
            MAP_ITEM("GB2312", GB2312, 936),
            MAP_ITEM("ASCII", ASCII, CP_ACP),
            MAP_ITEM("UTF-8", UTF_8, CP_UTF8),
            MAP_ITEM("EUC_JP", EUC_JP, 20932),
            MAP_ITEM("KOI8-R", KOI8_R, 20866),
            MAP_ITEM("GB18030", GB18030, 54936),
            MAP_ITEM("SHIFT_JIS", SHIFT_JIS, 932),
            MAP_ITEM("ISO-8859-1", ISO_8859_1, 28591),
            MAP_ITEM("ISO-8859-2", ISO_8859_2, 28592),
            MAP_ITEM("ISO-8859-3", ISO_8859_3, 28593),
            MAP_ITEM("ISO-8859-4", ISO_8859_4, 28594),
            MAP_ITEM("ISO-8859-5", ISO_8859_5, 28595),
            MAP_ITEM("ISO-8859-6", ISO_8859_6, 28596),
            MAP_ITEM("ISO-8859-7", ISO_8859_7, 28597),
            MAP_ITEM("ISO-8859-8", ISO_8859_8, 28598),
            MAP_ITEM("ISO-8859-9", ISO_8859_9, 28599),
            MAP_ITEM("ISO-2022-JP",ISO_2022_JP, 50222),
            MAP_ITEM("ISO-8859-13", ISO_8859_13, 28603),
            MAP_ITEM("ISO-8859-15", ISO_8859_15, 28605),
            MAP_ITEM("WINDOWS-1250", WINDOWS_1250, 1250),
            MAP_ITEM("WINDOWS-1251", WINDOWS_1251, 1251),
            MAP_ITEM("WINDOWS-1252", WINDOWS_1252, 1252),
            MAP_ITEM("WINDOWS-1253", WINDOWS_1253, 1253),
            MAP_ITEM("WINDOWS-1254", WINDOWS_1254, 1254),
            MAP_ITEM("WINDOWS-1255", WINDOWS_1255, 1255),
            MAP_ITEM("WINDOWS-1256", WINDOWS_1256, 1256),
            MAP_ITEM("WINDOWS-1257", WINDOWS_1257, 1257),
            MAP_ITEM("WINDOWS-1258", WINDOWS_1258, 1258),
            MAP_ITEM("MAC-CYRILLIC", MAC_CYRILLIC, 10007),
        };
        std::string temp(code);
        if (codePage.count(temp) == 0)              // 不被支持的编码 
        {
            break;
        }
        else {
            ret = codePage.find(temp)->second;
        }
// 现在, ret就是代码页信息, ret.id就是我们需要的代码页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#上的移植。

使用起来更简单:

DetectionResult encode = CharsetDetector.DetectFromFile(文件路径);

没了。记得探索一下encode的属性。这样就完事了。

然后,File.ReadAllText、StreamWriter等等都有指定编码的方式,需要注意的是,编码是System.Text.Encoding,它是DetectionResult里面一个特定元素的属性,并不是DetectionResult。

以上。

(编辑:天瑞地安资讯网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!