字符集與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++》,好像第二章就講到過這個問題,一直沒有實踐過,因此就忘記了。

相關文章
相關標籤/搜索