win 超大字符集汉字处理

发布时间 2023-09-22 16:29:47作者: XZY3031

win 处理超大汉字字符集汉字

1. 汉字编码方式简介

GB2312

​ 要介绍GB2312编码,首先需要介绍一下ASCII编码。

​ ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)是基于拉丁字母的一套电脑编码系统。其第一次以规范标准的形态发表是在1967年,至今共定义128个字符,由95个可打印字符(0x20-0x7E)和 33 个控制字符(0x00-0x1F,0x7F)组成。对于英语来说 ASCII 所表示的128个符号编码就足够了,但是对于其他语言,如法语,德语,汉语等无法使用ASCII码表示。对于汉语来说,汉字多达10万多个,而一个字节最多只能有256个状态,因此为了表示汉语,专家们提出了GB2312编码来进行汉语的编码,其使用两个字节表示一个汉字,这样理论上可以表示65536个符号。

​ GB2312是基于区位码设计的,在区位码的区号和位号上分别加上A0H就得到了GB2312编码。GB2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时,GB 2312收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。整个字符集分成94个区,每区有94个位。图 1 为GB2312汉字编码字符集对照表的一部分,从图中可以看到“场”对应的编码为 "B3A1"。

![GB2312 编码表](./fig/GB2312 编码表.png)

GBK

​ GBK(Chinese Internal Code Specification),即汉字内码扩展规范,K 为汉语拼音 Kuo Zhan(扩展)中的“扩”字的声母,于 1995 年 12 月发布的汉字编码国家标准。GBK是微软利用GB2312-80未使用的编码空间,收录GB13000.1-93全部字符指定了GBK编码,其是对GB2312-80的扩展,也是CP936字码表的扩展。GBK 共收录 21886 个汉字和图形字符,具体划分为:

  • GB2312 中的全部汉字( 6763 个)、非汉字符号
  • Big5(繁体字编码方式) 中的全部汉字
  • 与 ISO 10646 相应的国家标准 GB 13000 中的其他 CJK(中日韩) 汉字
  • 其他汉字、部首、符号,共计 984 个

其码位空间划分如下:

GBK码位划分空间

GB18030

​ GB 18030,国家标准 GB 18030-2005,是中国目前最新的内码字集,于 2000 年 3 月发布的汉字编码国家标准,与 GB 2312-1980 和 GBK 兼容,共收录汉字 70244 个:

  • 与 UTF-8 相同,采用多字节编码,每个字可以由 1 个、2 个或 4 个字节组成
  • 编码空间庞大,最多可定义 161 万个字符
  • 支持中国国内少数民族的文字,不需要动用造字区
  • 汉字收录范围包含繁体汉字以及日韩汉字

GB 18030 编码是一二四字节变长编码。

Big5

​ Big5,又成为大五码或五大码,是使用繁体中文的社区中最常用的电脑汉字字符集标准,收录了13060个汉字。中文码分为内码和交换码知名的中文交换码有CCCII、CNS11643。Big5虽普及于中国台湾、中国香港与中国澳门等繁体中文通行区,但长期以来并非当地的国家标准,而只是业界标准。2003年,Big5 被收录到CNS11643中文标准交换码的附录中,这个最新的版本被称为Big5-2003,Big5码是一套双字节字符集,使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为"高位字节",第二个字节称为"低位字节"。"高位字节"使用了0x81-0xFE,"低位字节"使用了0x40-0x7E,及0xA1-0xFE。

基于Unicode的编码方式

​ Unicode是一个字符集标准,旨在包含世界上所有的字符和符号。它定义了每个字符的唯一标识,称为Unicode码点(code point),通常用U+XXXX的形式表示。Unicode不是具体的编码方式,只是字符的标识。基于Unicode的编码方式有UTF-8,UTF-16,UTF-32,这些编码方式将Unicode字符转换为字节序列:

  • UTF-8:采用可变长度编码,常用于互联网,对ASCII兼容,英文字符占1个字节,其他字符占用更多字节
  • UTF-16:采用2字节或4字节编码,常用于Windows系统,基本多语言平面(BMP)内的字符占用2个字节,辅助平面字符占用4个字节。
  • UTF-32:使用4字节固定编码,每个字符都占用4个字节的空间,用于一些特殊应用。

2. Win 的汉字编码方式

在Windows操作系统下,用于汉字的字符编码方式有:GBK,GB2312,UTF-8,UTF-16,Big5。从实际的Windows应用程序开发的角度看,汉字的编码可以分为两个大类,一个是基于ANSI代码页的编码,一个是基于Unicode的编码。

Windows 代码页(ANSI 代码页)

​ Windows 代码页通常称为“ANSI 代码页”,是非 ASCII 值 (大于 127 的值) 表示国际字符的代码页。 这些代码页在 Windows Me 中本机使用,也可在 Windows NT 及更高版本上使用。在Windows系统中,ANSI编码一般表示系统默认的编码方式,而不是确定的某一种编码方式,简体中文中的ANSI编码默认的是GB系列的编码(GB2312、GBK、GB18030),繁体中文操作系统中ANSI编码默认指Big5编码。在Windows中,代码页是系统默认的,系统区域设置System Locale,可用于确认在不使用Unicode编码的程序中输入和显示字符的默认编码方案。下图为代码页与对应的代码标识的部分内容:

标识符 .NET名称 其他信息
936 gb2312 ANSI/OEM 简体中文 (中国、新加坡) ;简体中文 (GB2312)
950 big5 ANSI/OEM 繁体中文 (台湾;中国香港特别行政区) :中国传统 (Big5)
1252 Windows-1252 ANSI 拉丁语 1;西欧 (Windows)
54936 GB18030 Windows XP 及更高版本: GB18030 简体中文 (4 字节) ;简体中文 (GB18030)
65000 utf-7 Unicode (UTF-7)
65001 utf-8 Unicode (UTF-8)

​ Windows代码页具有很多类型,如: 单字节字符集 (SBCS) 、 双字节字符集(DBCS) 和多字节字符集 (MBCS) 代码页。其中的 DBCS 最初是为了扩展 SBCS 设计来处理日语和中文等语言而开发的。 DBCS 中的某些字符(包括用于编写英语的数字和字母)具有单字节代码值。 其他字符(如中文象形文字或日语汉字)具有双字节代码值。

UTF - 16

unicode 定义了单字符集的多个编码:UTF-7、UTF-8、UTF-16 和 UTF-32。 这些编码之间的数据转换是无损的。Windows SDK 启用Unicode的函数均使用 UTF-16(宽字符)编码,该编码也是 Windows 操作系统上用于本机 Unicode 编码的编码。这与使用 8 位代码值的字符和字符串数据的旧代码页方法不同。虽然 Windows 中启用了 Unicode 的函数使用 UTF-16,但也可以使用在 UTF-8 或 UTF-7 中编码的数据,这些数据在Windows中支持多字节字符集(MBCS,Multi-Byte Character Set)的代码页上,比如上述表格中的标号为65000和65001的代码页即对应utf-7和utf-8代码页。新的 Windows 应用程序应使用 UTF-16 作为其内部数据表示形式。

3. Windows 对于字符串的处理

数据类型选择,处理和编译

Windows 支持三组字符和字符串数据类型:一组可为 Unicode 或 Windows 代码页编译的泛型类型定义,以及两组特定类型定义。 一组特定类型定义用于 Unicode,另一组用于 Windows 代码页。以下是Windows头文件中用于定义三组数据类型的方法,在编译过程中由预处理器指令定义 UNICODE 时,将字符串标识为 Unicode。 否则,宏会将字符串标识为 ANSI 字符串:

// Generic types
// 其中前缀为T的可指定为Windows代码页或Unicode编译的泛型类型,定义中的"w"代表 Unicode
// 简单类型定义如:CHAR和LPSTR 代表 Windows代码页
// 若要编译 Windows 代码页,应用程序只需省略 UNICODE 定义。
#ifdef UNICODE
    typedef wchar_t TCHAR;
#else
    typedef unsigned char TCHAR;
#endif

typedef TCHAR *LPTSTR, *LPTCH;

// Windos代码页的类型

typedef unsigned char CHAR;
typedef CHAR *LPSTR, *LPCH;

// Unicode 类型(宽字符串)

typedef unsigned wchar_t WCHAR;
typedef WCHAR *LPWSTR, *LPWCH;

以下为三种数据类型对应的 SetWindowText 函数的示例:

  1. 首先根据宏的定义来决定使用哪一种数据类型
// 标头文件提供以宏形式实现的通用函数名称。
#ifdef UNICODE
#define SetWindowText SetWindowTextW // W 代表宽字符 Unicode
#else
#define SetWindowText SetWindowTextA // A 代表窄字符 ANSI
#endif // !UNICODE
  1. 对于Windows 代码页的情况:

虽然目前 win 的字符集编码标准是 UTF-16,但是许多旧版的应用程序继续使用基于代码页的字符集,每个代码页由代码页标识符表示,对于这种情况,需要使用具有“A” (ANSI) 版本的Windows API。 “A”版本处理基于 Windows 代码页的文本。Windows操作系统始终有一个当前处于活动状态的Windows代码页,所有的ANSI版本的API函数使用当前活动的代码页。该函数的原型如下所示:

// 用于 ANSI 的原型
BOOL SetWindowTextA(
  HWND hwnd, // 窗口句柄 window handle,用来唯一的标识窗口
  LPCSTR lpText  // 传递要设置的窗口文本内容
);
  1. 对于UTF-16的情况

目前许多的应用程序主要使用UTF-16编码,在 win 中使用具有“W” (宽的 Unicode) 版本 Windows API 来进行处理,“W”版本用于处理 Unicode 文本。函数原型如下所示:

// 用于 Unicode 的原型
BOOL SetWindowTextW(
  HWND hwnd,
  LPCWSTR lpText   
);

Windows 代码页和Unicode字符串之间的转换

使用Windows SDK 提供的 MultiByteToWideCharWideCharToMultiByte 可以进行代码页和Unicode字符串在字符串之间的转换,注意这些函数适用于单字节字符集 (SBCS) 、双字节字符集 (DBCS) 和多字节字符集 (MBCS) 代码页函数,他们的语法如下:

int MultiByteToWideChar(
  [in]            UINT                              CodePage,      // 代码页标识符
  [in]            DWORD                             dwFlags,       // 指示转换类型
  [in]            _In_NLS_string_(cbMultiByte)LPCCH lpMultiByteStr,// 指向转换后的字符串的缓冲区的指针
  [in]            int                               cbMultiByte,   // lpMultiByteStr指示的缓冲区大小
  [out, optional] LPWSTR                            lpWideCharStr, // 指向要转换的 Unicode 字符串的指针
  [in]            int                               cchWideChar    // 要处理的字符数,-1代表都处理
);

int WideCharToMultiByte(
  [in]            UINT                               CodePage,
  [in]            DWORD                              dwFlags,
  [in]            _In_NLS_string_(cchWideChar)LPCWCH lpWideCharStr,
  [in]            int                                cchWideChar,
  [out, optional] LPSTR                              lpMultiByteStr,
  [in]            int                                cbMultiByte,
  // 如果无法在指定的代码页中表示字符,则指向要使用的字符的指针
  [in, optional]  LPCCH                              lpDefaultChar,
  // 指向标志的指针,该标志指示函数是否在转换中使用了默认字符。 
  [out, optional] LPBOOL                             lpUsedDefaultChar 
);

对于UTF - 8 和 GBK 之间的转换,需要二者同时转换为 unicode 码点后才能转换为对方。

此外,需要注意的是,大部分的转换不是无损的,因为每个代码页支持不同的字符,但没有一个页面支持 Unicode 提供的完整字符范围。 每个代码页都支持不同的子集,编码方式不同。 从一个 DBCS 代码页转换为另一个 DBCS 代码页的数据可能会损坏,因为不同代码页上的相同数据值可以编码不同的字符。 从 Unicode 转换为 DBCS 的数据可能会丢失数据,因为给定的代码页可能无法表示该特定 Unicode 数据中使用的每个字符。

字符的识别

ANSI 代码页字符集的识别(SBCS,DBCS)

SBCS代码页的识别相对DBCS要简单,对于DBCS,若要解释 DBCS 字符串,应用程序必须在字符串的开头启动并向前扫描。 它在字符串中遇到前导字节时会跟踪,并将下一个字节视为同一字符的尾随部分。 如果应用程序只是一次扫描一个字节的字符串,并遇到一个字节,该字节似乎是表示反斜杠 (“\”) 的代码值,则该字节可能只是两字节字符的尾随字节。 应用程序不能只备份一个字节来查看前面的字节是否为前导字节,因为该字节值可能有资格同时用作前导字节和尾随字节。

不过,虽然二者识别难易不同,但是应用程序都可以使用GetCPInfoGetCPInfoEx 函数来处理,此外对于DBCS,应用程序可以使用 IsDBCSLeadByte 函数来确定给定值是否可用作 2 字节字符的前导字节。

UTF-16字符集的识别

IsTextUnicode 用来确定缓冲区是否可能包含 Unicode 文本形式。