字符编码(Character encoding),计算机的CPU计算、硬盘存储、显示器都是以数字信号存在的。那么所有显示的字符(无论是英文字符、还是中文字符)都是以数字来表示的。字符编码即统一套标准来在不同计算机之间存储传递信息。这一套以数字表示字符的过程,就是字符编码过程。
字符集(Character Set),世界上有很多种文字,每种文字都有一套自己的字库。很多国家为了让计算机正常显示自己的文字,发明了套字符编码方式。这样每个字都有一个数字编码相对应。这种以数字编码应对所有字的集合,就是字符集。
一般情况下,不同文字体系都有一套字符集。如英文是ASCII字符集,简体中文是GBK字符集,繁体字是Big5字符集。字符集太多,不利于信息在全球传递。所以需要一种可以包含所有文字体系的字符集,这种字符集就是Unicode字符集。
ASCII字符集,我们知道计算机最早是美国发明的,所以字符编码这一块儿最早也是美国弄的。ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):使用 7 个 Bit 表示,共 128 个字符,刚好占用了一个字节中的后 7 位,共包括33个控制字符和95个可显示字符。这128个数字编码对应的字符集即是ASCII字符集。
1984 年台湾五家公司联合创立,称大五码,英文 Big5。Big5 也是双字节编码方式,收录了一万多字符,但是没有包含中文简体。Big5 目前也被香港、澳门等地区国家使用。Big5 保存的文本,在简体操作系统下显示乱码。为什么呢?这是因为Big5字符集中的繁体字对应的编码与GBK字符集中的繁体字对应的编码不一样。所以Big5编码的繁体内容文本是无法直接用GKB编码方式解码并显示的。
但是有些编辑器会自动识别出是Big5编码,然后将其按 GBK 繁体字解码,即可以在简体中文操作系统下正常显示。如 Notepad++、Visual Studio Code均可以在简体操作系统下显示Big5编码的文本内容。
Unicode字符集,随着不同国家间越来越紧密的联系,尤其是Web的发展。任何一个网页都可以在全球有正常网络的地方打开浏览。如果新浪网的内容是以GBK编码的,那么在国际的华人可能就无法正常浏览网页内存了。基于信息的全球互通,一个Unicode的国际组织成立,创建了一种Unicode字符集,这种字符集可以容纳所有国家的所有字符编码。
早期的Unicode字符集采用的编码方式是以4个BYTE表示一个字符。那么一个汉字只需要1个Unicode编码表示就可以,但是英文单词却需要很多个Unicode字符表示,这种方式大大增加了英文内存的存储、传输开销。所以Unicode早期一直无法很好地在全球推行。英文内存提供商依然使用传统的ASCII字符编码。采用4BYTE表示一个字符的编码方式被称为UTF-32。
比较ASCII以及UTF-32各自的优点,一个新的Unicode编码方式采取了一个折中的方案,即UTF-16。世界主流字符用2BYTE即可以表示,但是也必须照顾到那些不常用的字符。那么怎么办呢?
UTF-16将常用的字符安排在0-0xFFFF这个范围,而不常用的安排在0xFFFF之后。那么如何处分0x0001和0x10000这两个字符呢?Unicode委会员商议,将0xD800-0xDFFF这个范围空出来,用来表示是2BYTE的字符,还是4BYTE的字符。Windows下,UTF-16也被称为宽字符。
UTF-16相比UTF-32,大幅降低了信息传递、存储的消耗。但是英文字符的国家依然觉得增加近一倍的消耗是不可以接受的。Unicode字符集依然难以在世界范围内推行。Unicode急需要一种能够让各方都满意地编码方式才行。在这种情况,UTF-8出现了。UTF-8编码方式的设计是完全兼容ASCII编码的,这样就不会增加英文字符的开销,并且可以完整表达所有的Unicode字符集。
我们知道标准的ASCII字符集只表示128个字符,最高位是没有使用的。在UTF-8编码中,如果1BYTE中最高Bit为0,则当前BYTE和ASCII编码相同。如果最高Bit为1,则表示当前BYTE是Unicode编码,接下来有几个1,就表示这个字符的Unicode编码占有几个BYTE。如0x1111 0XXX XXXX XXXX,就表示这个字符占用4个BYTE。
UTF-8是变字节长度的,用 1-6 个字节表示字符。用一个字符表示常见字符,用二个字节表示拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚等,用三字符表示常见汉字等,四至六个字节的字符比较少见。
UTF-8这么好,是不是大家都开始用UTF-8呢?不一定。UTF-8兼容性好,但正因为要兼容各种情况,所以解析内容的时候随时需要判断,效率低。所有有一些操作系统,如Windows的Unicode编码方式采用了UTF-16,像Java的字符串编码也是采用UTF-16,C++新标准支持UTF-16、UTF-32。新语言Rust更是直接采用了UTF-32编码方式,相当于空间换效率。
代码页(CodePag):,代码页是字符集的数字值,不同的语言使用不同的代码页。例如,ANSI代码页为 1252,日文代码页为 932,简体中文(GBK)代码页为 936,繁体中文(Big5)代码页为 950。
更多代码页信息见:https://docs.microsoft.com/en-us/previous-versions/aa913244(v=msdn.10)
本地化(Locale)在这里是指操作系统中的一个概念,即操作系统的相关信息要本地化,主要是指语言编码及显示的本地化,也即执行代码和UI显示的本地化。
Windows操作系统的UI文字显示默认是与当前操作系统环境语言一致。Windows支持修改当前UI文字的显示语言。即通过Control Panel\All Control Panel Items\Region and Language\Keyboards and Language\Display language\Choose a display language来安装并选择相应的语言包,即可以显示相应的语言。
操作系统语言包主要有2个作用:
// UTF-16在Windows中即代表Unicode,也即宽字符集WCS
// MBCS,多字节字符集,泛指GBK、BIG5等字符集
wchar_t szUnicode[32] = {
0};
char szTran[32] = {
0xc3, 0x61};
if (-1 == mbstowcs(szUnicode, szTran, sizeof(szTran)))
{
return TRUE;
}
char szSimp[32] = {
0};
if (-1 == wcstombs(szSimp, szUnicode, sizeof(szSimp)))
{
return TRUE;
}
int GBK2UTF8(char *szGbk,char *szUtf8,int Len)
{
// 先将多字节 GBK(CP_ACP 或 ANSI)转换成宽字符 UTF-16
// 得到转换后,所需要的内存字符数
int n = MultiByteToWideChar(CP_ACP,0,szGbk,-1,NULL,0);
// 字符数乘以 sizeof(WCHAR) 得到字节数
WCHAR *str1 = new WCHAR[sizeof(WCHAR) * n];
// 转换
MultiByteToWideChar(CP_ACP, // MultiByte 的代码页 Code Page
0, //附加标志,与音标有关
szGbk, // 输入的 GBK 字符串
-1, // 输入字符串长度,-1 表示由函数内部计算
str1, // 输出
n // 输出所需分配的内存
);
// 再将宽字符(UTF-16)转换多字节(UTF-8)
n = WideCharToMultiByte(CP_UTF8, 0, str1, -1, NULL, 0, NULL, NULL);
if (n > Len)
{
delete[]str1;
return -1;
}
WideCharToMultiByte(CP_UTF8, 0, str1, -1, szUtf8, n, NULL, NULL);
delete[]str1;
str1 = NULL;
return 0;
}
int UTF82GBK(char *szUtf8,char *szGbk,int Len)
{
int n = MultiByteToWideChar(CP_UTF8, 0, szUtf8, -1, NULL, 0);
WCHAR * wszGBK = new WCHAR[sizeof(WCHAR) * n];
memset(wszGBK, 0, sizeof(WCHAR) * n);
MultiByteToWideChar(CP_UTF8, 0,szUtf8,-1, wszGBK, n);
n = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
if (n > Len)
{
delete[]wszGBK;
return -1;
}
WideCharToMultiByte(CP_ACP,0, wszGBK, -1, szGbk, n, NULL, NULL);
delete[]wszGBK;
wszGBK = NULL;
return 0;
}
// 950即繁体的CodePage,936即简体的CodePage
wchar_t szUnicode[32] = {
0};
char szTran[32] = {
0xc3, 0x61};
setlocale(LC_ALL, ".950");
if (-1 == mbstowcs(szUnicode, szTran, sizeof(szTran)))
{
return TRUE;
}
setlocale(LC_ALL, ".936");
char szSimp[32] = {
0};
if (-1 == wcstombs(szSimp, szUnicode, sizeof(szSimp)))
{
return TRUE;
}
// Cstring转换Unicode
CString str(_T("Soccer is best!"));
BSTR pstrUni = str.AllocSysString();
SysFreeString(pstrUni);
// ATL Unicode和MBCS转换
USES_CONVERSION;
CString tmpStr;
char*LineChar="fdsfdsa";
const WCHAR * cLineChar = A2W(LineChar);
USES_CONVERSION;
CString tmpStr;
WCH LineChar="fdsfdsa";
const char* cLineChar = A2W(LineChar);
我们知道Windows操作系统是可以设置不同的语言的。不同语言相当一个语言包,一种语言包可能几十MB,语言多了,这个语言包也比较大。所以windows默认是不包括其他语言的语言包的。如,简体中文操作系统需要显示为英文,需要安装英文语言包。英文操作系统要显示中文,要安装东亚语言包(包括中文、日文、韩文)。安装好语言包,选择对应语言,重启,即可以显示相应的语言。GetUserDefaultUILanguage可以获取安装语言包后选择的语言人LCID(如下图)。GetSystemDefaultUILanguage和GetSystemDefaultLangID的作用不同,详细MSDN。