字符集与Mysql字符集处理(一)

 

1、字符集总结html

其实大多数的知识在这篇文章里已经讲得很是清楚了。这里只是讲一下本身的感悟。linux

1. UTF-8虽然是以UTF(unicode transfermation format)开头的,可是他并非真正意义上的Unicode。他是在UCS上的再编码。并且,这是一个变长的编码方式。ios

2. 根据这篇文章的说法,在ISO制定UCS(Universal Character Set)的同时,另外一个由厂商联合组织也在着手制定这样的编码,称为Unicode,后来两家联手制定统一的编码,但各自发布各自的标准文档,因此UCS编码和Unicode码是相同的。c++

3. 当咱们在Linux下采用“locale –a”命令查看全部可用字符集时,出现的描述其实就是都是属于Native ANSI,由于他们都是标准ANSI的超集。windows

4. 当咱们在Windows下将文件以UTF-8的方式进行存储时,Windows会将一个叫作UTF-8 Signature的三个字节写在文件的最开头,这样就能够表示,这个文件的编码方式就是UTF-8,而不是其余字符集。因为UTF-8是变长编码,因此这样就便于咱们去区分以下的一种状况,即当文件中只有标准ANSI字符集中规定的字符时,咱们还认为这是一个UTF-8编码,这样之后若是再出现CJK(Chinese,Japan,Korea)里面的汉字时,就能够顺利解码了。一样,也是由于它,当咱们在windows下另存为UTF-8格式时,直接FTP到Linux下进行编译,即便将gcc的-finput-charset=utf-8设定,也会出现编译错误(由于有不认识的字符了,具体表现如此)。网络

5. 一般来讲,咱们所存储的文件(这里主要是指配置文件,代码文件)须要使用UTF-8编码。一方面是由于gcc的默认finput-charset选项是utf-8,另一方面utf-8编码支持全部的字符(中文,英文)。 *.Java文件就是这样存储的。ide

6. 注意到一个专有名词叫作“C Locale”,他是通常C语言程序进入main以后的默认字符集(能够经过setlocale(LC_ALL, NULL)进行查看)。这个字符集就是ANSI字符集,由于全部的C程序都支持这个字符集,且有了这个字符集就能够运行程序了,因此就给了一个名字,叫作“C Locale”。函数

 

2、关于gcc对字符集的支持工具

这里所述的内容,主要是参考了这篇文章和man gcc。这里作一个总结。在整个编译过程当中有以下几个关键的字符集。编码

  • 代码文件的字符集A
  • gcc内部处理的字符集B(UTF-8)
  • gcc输出的二进制文件的字符集C(默认是UTF-8,可使用-fexec-charset选项进行指定)

具体来讲,当咱们运用gcc命令将代码文件进行编译,它就直接认为代码文件的字符集是finput-charset选项中所指定的字符集(默认是utf-8),也就是说他也许根本就不知道你的字符集是字符集A。他根据finput-charset选项中的字符集向本身的内部所使用的字符集B进行转码。通过编译以后,就再次将二进制输出从内部字符集B转为字符集C。图示为,

 

image

 

学过编译原理的同窗应该会知道,在二进制文件中最多的应该是指令,那么什么是须要使用字符集C来表示的?固然是咱们硬编码的字符串。直接嫁接这里的例子,若是有以下代码,

#include <stdio.h>

int main(void)
{
	printf("你好\n");
	return 0;
}

 

且咱们假设,源文件是UTF-8编码,咱们也使用默认的gcc –fexec-charset。那么这个“你好”就须要使用字符集C进行编码,经过命令查看

$ od -tc nihao.c 
0000000   #   i   n   c   l   u   d   e       <   s   t   d   i   o   .
0000020   h   >  \n  \n   i   n   t       m   a   i   n   (   v   o   i
0000040   d   )  \n   {  \n  \t   p   r   i   n   t   f   (   " 344 275
0000060 240 345 245 275   \   n   "   )   ;  \n  \t   r   e   t   u   r
0000100   n       0   ;  \n   }  \n
0000107

 

其中八进制的344 375 240(十六进制e4 bd a0)就是“你”的UTF-8编码,八进制的345 245 275(十六进制e5 a5 bd)就是“好”。

由此能够获得的结论是,尽可能使用UTF-8来编写咱们的源程序,这样就能够不用显式设置-finput-charset和-fexec-charset了,便于移植。

3、程序运行与字符集

1. printf(“%s”)到底作了什么?参考文档。

当咱们在程序里面使用了printf(“%s”),他其实就是把字符串首地址到第一个“\0”处的字节write到当前终端的设备文件。若是当前终端的驱动程序可以识别UTF-8编码就能打印出汉字,若是当前终端的驱动程序不能识别UTF-8编码(好比通常的字符终端)就打印不出汉字。也就是说,像这种程序,识别汉字的工做既不是由C编译器作的也不是由libc作的,C编译器原封不动地把源文件中的UTF-8编码(假设这个源文件就是用UTF-8编码且没有另外指定-finput-charset)复制到目标文件中,libc只是看成以0结尾的字符串原封不动地write给内核,识别汉字的工做是由终端的驱动程序作的

我一开始觉得终端会帮咱们作转码,由于咱们设置了“LANG”这个环境变量(他会转而设置LC_ALL),因此作了以下实验。

#include <iostream>
#include <locale.h>
using namespace std;
  
int main(int argc, char **argv)
{
    string s = "你好";
    cout << s << endl;

    char buff[10] = "你好";
    for (int i = 0; i < 10; i++)
    {
        printf("%2X ", buff[i]);
    }
    cout << endl;
      
    return 0;
}

如今我保证输出的二进制是UTF-8编码的。实验以下,

image

 

能够看到,输出和当前终端的字符集无关。由于从程序里面输出的字节流就是“你”和“好”的UTF-8编码,因此仍是被设备驱动程序给正确解析了。

 

2. setlocale()到底用来作什么?

在作上面的实验的时候,其实我还对“终端字符集对输入和输出会进行转码”而有所期待。因此在代码里,我还特地尝试了个中setlocale(LC_ALL, “xxxx”)的调用,尝试看结果。可是结果老是和上面所说的同样,老是没有出现乱码输出。

通过一段分析和朋友的点拨,终于将setlocale与wcstombs(宽字符串转换到多字节串)、mbstowcs(多字节串转换到宽字符串)结合起来了。

要讲清楚这个事情,必须先从宽字符串(wide-character string)与多字节串(multibyte string)讲起。为何要引入宽字符串,这里有比较好的讲法。摘抄以下。

“最根本的缘由是,ANSI下的字符串都是以’\0’来标识字符串尾的(Unicod以“\0\0”束),许多字符串函数的正确操做均是以此为基础进行。而咱们知道,在宽字符的状况下,一个字符在内存中要占据一个字的空间,这就会使操做ANSI字符的字符串函数没法正确操做。以”Hello”字符串为例,在宽字符下,它的五个字符是:

0x0048 0x0065 0x006c 0x006c 0x006f 
在内存中,实际的排列是:

48 00 65 00 6c 00 6c 00 6f 00  (这里应该是源代码是UTF-8编码的,因此高位是00——Aicro注)

因而,ANSI字符串函数,如strlen,在碰到第一个48后的00时,就会认为字符串到尾了,用strlen对宽字符串求长度的结果就永远会是1! ”

 

其实这里所说的宽字符串,就是咱们一般所说的Unicode串,也就是UCS-2。

mbstowcs就是将,反之是wcstombs。

 

  • mbstowcs的具体工做流程

咱们先经过在线转换工具了解到“你好”这两个汉字的宽字符(Unicode)表示是\u4f60\u597d。

 

下面的程序使用gcc编译的,使用各类默认选项。

#include <locale.h>
#include <iostream>
#include <stdlib.h>
#include "string.h"

int main()
{
    char* source = "你好";
    
    setlocale(LC_ALL, "zh_CN.utf8");
    
    // 获取长度
    size_t wcs_size = mbstowcs(NULL, source, 0);
    
    // 申请内存并初始化
    wchar_t* dest = new wchar_t[wcs_size + 1];
    wmemset(dest, L'\0', wcs_size + 1); 
    
    // 多字节串转换到宽字符串,注意,第三个参数是byte数
    mbstowcs(dest, source, strlen(source) * sizeof(char));
    
    // 验证一下
    for (int i = 0; i < wcs_size; i++)
    {
        printf("%2X ", dest[i]);
    }
    
    printf("\n");
    
    return 0;
}

 

输出结果就是4F60 597D。可见,mbstowcs的做用过程是

 

image

重点是,mbstowcs把LC_CTYPE认做是source(多字符字串)的编码。

 

  • wcstombs的具体工做流程

咱们先经过在线转换工具了解到“你好”这两个汉字的gbk编码是C4 E3 BA C3 。

实验程序

#include <locale.h>
#include <iostream>
#include <stdlib.h>
#include "string.h"

int main()
{
    char* source = "你好";
    
    setlocale(LC_ALL, "zh_CN.utf8");
    
    // 获取长度
    size_t wcs_size = mbstowcs(NULL, source, 0);
    
    // 申请内存并初始化
    wchar_t* dest = new wchar_t[wcs_size + 1];
    wmemset(dest, L'\0', wcs_size + 1); 
    
    // 多字节串转换到宽字符串,注意,第三个参数是byte数
    mbstowcs(dest, source, strlen(source) * sizeof(char));
    
    // 转回gbk编码
    setlocale(LC_ALL, "zh_CN.gbk");
    
    // 获取长度
    size_t mbs_size = wcstombs(NULL, dest, 0);
    
    // 申请内存并初始化
    char* buf_mbs = new char [mbs_size + 1];
    memset(buf_mbs, '\0', mbs_size + 1);
    
    // 宽字符串转换到多字节串,注意,第三个参数是byte数
    wcstombs(buf_mbs, dest, wcs_size * sizeof(wchar_t));
    
    // 验证一下
    for (int i = 0; i < mbs_size; i++)
    {
        printf("%2X ", buf_mbs[i]);
    }
    
    printf("\n");    
    
    return 0;
}

 

输出结果与预期同样。这说明了wcstombs的流程。

image

重点是,wcstombs把LC_CTYPE认做是destination(多字符字串)的编码。

 

总结:setlocale须要与wcstombs域mbstowcs联合使用。根据这篇文章的说法,程序在作内部计算时一般以宽字符编码,若是要存盘或者输出给别的程序,或者经过网络发给别的程序,则采用多字节编码。这就让我想到了原来读第四版《windows via c/c++》,好像第二章就讲到过这个问题,一直没有实践过,因此就忘记了。

相关文章
相关标签/搜索