關於解決亂碼問題的一點探索之一(涉及utf-8和GBK)


在使用Visual Studio 2005進行MFC開發的時候,發現自動添加的注釋變成了亂碼。像這樣:

// TODO: ÔÚ´ËÌí¼ÓרÓôúÂëºÍ/»òµ÷ÓûùÀà
還有這樣:
// TODO: ÔÚ´ËÌí¼ÓÏûÏ¢´¦Àí³ÌÐò´úÂëºÍ/»òµ÷ÓÃĬÈÏÖµ

它們正確的顯示應該是

// TODO: 在此添加專用代碼和/或調用基類
   和
// TODO: 在此添加消息處理程序代碼和/或調用默認值

當保存的時候,還出現了這樣的對話框:

image

網上找了各種教程,包括什么設置“自動識別不帶簽名的utf-8”什么的,都沒有用。所以考慮自己解決。下面是我的探索過程:

一,保存文件

    首先,將文件以“Unicode(UTF-8帶簽名) 代碼頁:65001”的形式進行保存(帶簽名的UTF-8是指有BOM的UTF-8,至於帶BOM和不帶BOM的UTF-8有什么區別,請戳此)。如下圖:

image

 

二,查看文件的16進制代碼(就是查看文件實際上保存成什么數據了)

    使用WinHex軟件打開剛剛保存的文件(當然,使用UltraEdit也可以),查看文件的16進制代碼。我們找到亂碼的地方,把它的16進制代碼找出來,如下:

image

    為了更清楚地演示,我將亂碼單獨拷出來,一定要注意將文本保存成UTF-8(最好帶BOM,如果使用Windows自帶的文本編輯器編輯就自帶BOM)保存成這樣:

image

 

    文件對應的16進制代碼為:

image

    最前面的三個字節“EF BB BF”就是前面所述的BOM標記,從第四個字節開始,就是文件的實際內容。觀察后發現,在實際內容部分,奇數位上不是C2就是C3,偶數位的沒有規律,同時,我們找出原話“在此添加專用代碼和/或調用基類”對應的GBK編碼值,進行比較。

表一:亂碼文件中的16進制數據:

0xc3    0x94    0xc3    0x9a    
0xc2    0xb4    0xc3    0x8b    
0xc3    0x8c    0xc3    0xad    
0xc2    0xbc    0xc3    0x93    
0xc3    0x97    0xc2    0xa8    
0xc3    0x93    0xc3    0x83    
0xc2    0xb4    0xc3    0xba    
0xc3    0x82    0xc3    0xab    
0xc2    0xba    0xc3    0x8d    
0x2f
0xc2    0xbb    0xc3    0xb2    
0xc2    0xb5    0xc3    0xb7    
0xc3    0x93    0xc3    0x83    
0xc2    0xbb    0xc3    0xb9    
0xc3    0x80    0xc3    0xa0

表二:“在此添加專用代碼和/或調用基類”對應的GBK編碼值,每個字符(漢字或者/)對應一行:

0xd4    0xda    
0xb4    0xcb    
0xcc    0xed    
0xbc    0xd3    
0xd7    0xa8    
0xd3    0xc3    
0xb4    0xfa    
0xc2    0xeb    
0xba    0xcd    
0x2f
0xbb    0xf2    
0xb5    0xf7    
0xd3    0xc3    
0xbb    0xf9    
0xc0    0xe0

4,分析

     仔細觀察上面的兩個表中的數據,我們不難發現以下規律:

    1,將表一每行中奇數位置(除了’/’那行)的C2、C3的值去掉,剩下的值和表二中的數據高度相似。

    2,除了’/’那行,表一每行中奇數位置為C2的,后面的偶數位數字就和表二中對應位(表一種第二列對應表二第一列,表一第四列對應表二中第二列)相同,表一每行中奇數位為C3的,后面的偶數位數字加上16進制數0x40后也與表二中對應位相同(對應法則同前)。

    3,由於亂碼文件是以utf-8存儲的,但是經過轉換后得到的編碼為GBK,我們大致可以知道,出現亂碼的原因就是visual studio 2005將兩種編碼搞混了,這應該算是一個bug吧。。畢竟visual studio 2013就從來沒有碰到過。。

5,解決問題

     根據上面的規律,我們使用二進制方式讀取utf-8格式編碼的文件數據后經轉化然后輸出到GBK編碼的文件中即可修正問題了。

    按照以上的規律編寫一段簡單的C語言程序:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    FILE* fp;
    FILE* fp2;
    //打開存儲亂碼的文件,utf-8格式,二進制打開
    if((fp2=fopen("BadCode.txt","rb+"))==NULL)
    {
        printf("Open Source File Failed!\n");
        system("pause");
        exit(1);
    }
    //打開、新建存儲處理后數據的文件
    if((fp=fopen("BadCodeH.txt","w+"))==NULL)
    {
        printf("Open/Create Destination File Failed!\n");
        system("pause");
        exit(1);
    }
    //紀錄奇數位(高位)的數據
    unsigned ch;
    //紀錄偶數位(低位)的數據
    unsigned cl;
    //獲得數據
    ch=fgetc(fp2);
    //判斷文件的格式,utf-8或者Unicode,並跳過BOM字符
    if(ch==0xef)
    {
        fgetc(fp2);
        fgetc(fp2);
        ch=fgetc(fp2);
    }
    else if(ch==0xff)
    {
        fgetc(fp2);
        ch=fgetc(fp2);
    }
    //不達結尾
    while(!feof(fp2))
    {
        //ASCII字符,正常輸出
        if(ch<=0x7f)
        {
            fputc(ch,fp);
        }
        //奇數位為0xC3,獲得偶數位后加0x40后輸出
        else if(ch==0xc3)
        {
            cl=fgetc(fp2);
            cl+=0x40;
            fputc(cl,fp);
        }
        //奇數位為0xC2,獲得偶數位后直接輸出
        else if(ch==0xc2)
        {
            cl=fgetc(fp2);
            fputc(cl,fp);
        }
        //其他情況,直接輸出
        else
        {
            fputc(ch,fp);
        }
        //獲得下一個數據
        ch=fgetc(fp2);
    }
    
    fclose(fp);
    fclose(fp2);
    system("pause");
    return 0;
}

操作實例結果如下圖:

image

6,更一般的情況(既有正確的中文字符又有亂碼)

    我們必須注意到一點:上面的C語言程序只適合一種情況:就是亂碼文檔格式為utf-8且文檔中只存在中文亂碼字符與ASCII字符。但是我們很多時候是源碼中既有正確的中文字符又有亂碼字符,這時上面的程序就無效了,因為我們需要將正確中文字符的utf-8編碼轉換為GBK編碼才可以。我們嘗試修改上面的代碼來解決這個問題。

    對於既有亂碼又有正常字符的文件來說,只要將正確的中文字符的utf-8編碼轉化為GBK編碼就解決問題了,所以主要問題的關鍵就是建立一個utf-8與GBK編碼的轉換表。baidu一下,我們很容易找到了這個表,然后,就寫了以下的程序。UnicodeToGBK.txt文件請戳下載地址

 

#include <stdio.h>
#include <stdlib.h>
//將gbk編碼值存入數組中utf-8編碼對應的位置上
bool ReadTable(unsigned* mapValue2)
{
    //聲明文件指針
    FILE* fp;
    //打開轉換表文件,文件中第一列為漢字的GBK編碼,第二列為utf-8編碼
    //以可讀寫方式打開
    if (NULL == (fp = fopen("Utf8ToGBKTable.txt", "r+")))
    {
        printf("Open Table Failed!");
        system("pause");
        return false;
    }
    //記錄gbk的編碼值
    unsigned gbk=0;
    //臨時記錄各位數據
    unsigned data;
    //記錄utf-8的編碼值
    unsigned long utf=0;
    //循環次數記號
    unsigned id=0;
    while(!feof(fp))
    {
        //獲得第一列gbk的編碼值
        for (int i = 0; i < 4; ++i)
        {
            data=fgetc(fp);
            data=data>='A'?data-'A'+10:data-'0';
            gbk=gbk*16+data;
        }
        //跳過tab鍵
        fgetc(fp);
        //獲得第二列utf-8的編碼值
        for (int i = 0; i < 6; ++i)
        {
            data=fgetc(fp);
            data=data>='A'?data-'A'+10:data-'0';
            utf=utf*16+data;
        }
        if (id>6350)
        {
            printf("%d\t%ld\n",gbk,utf );
        }
        mapValue2[utf-14989440]=gbk;
        fgetc(fp);
    //    fgetc(fp);
        //重置數據
        gbk=0;
        utf=0;
        id++;
    }

    fclose(fp);
    return true;
}

int main(int argc, char const *argv[])
{
    FILE* fp;
    FILE* fp2;
    //打開存儲亂碼的文件,utf-8格式,二進制打開
    if((fp2=fopen("BadCode.txt","rb+"))==NULL)
    {
        printf("Open Source File Failed!\n");
        system("pause");
        exit(1);
    }
    //打開、新建存儲處理后數據的文件
    if((fp=fopen("BadCodeH.txt","w+"))==NULL)
    {
        printf("Open/Create Destination File Failed!\n");
        system("pause");
        exit(1);
    }
    unsigned mapValue2[330000];
    if(!ReadTable(mapValue2))
  {
  

     printf("Convert Failed!\n");
  
     system("pause");
      exit(1);

  }

//紀錄奇數位(高位)的數據 unsigned ch; //紀錄偶數位(低位)以及utf-8高位的數據 unsigned cl; //記錄utf-8末位字節的信息 unsigned cu; ////記錄正常字符的utf-8編碼 unsigned long utf; //記錄正常字符的gbk編碼 unsigned cgbk; //獲得數據 ch=fgetc(fp2); //判斷文件的格式,utf-8或者Unicode,並跳過BOM字符 if(ch==0xef) { fgetc(fp2); fgetc(fp2); ch=fgetc(fp2); } else if(ch==0xff) { fgetc(fp2); ch=fgetc(fp2); } //不達結尾 while(!feof(fp2)) { //ASCII字符,正常輸出 if(ch<=0x7f) { fputc(ch,fp); } //奇數位為0xC3,獲得偶數位后加0x40后輸出 else if(ch==0xc3) { cl=fgetc(fp2); cl+=0x40; fputc(cl,fp); } //奇數位為0xC2,獲得偶數位后直接輸出 else if(ch==0xc2) { cl=fgetc(fp2); fputc(cl,fp); } //其他情況,即正常utf-8字符,轉換為GBK字符后輸出 else { //獲得utf-8的中位字節 cl=fgetc(fp2); //獲得utf-8的末尾字節 cu=fgetc(fp2); //計算utf-8編碼 utf=ch*65536+cl*256+cu; //獲得對應的gbk編碼 cgbk=mapValue2[utf-0xe4b880]; //輸出數據 fputc(cgbk/256,fp); fputc(cgbk%256,fp); } //獲得下一個數據 ch=fgetc(fp2); } fclose(fp); fclose(fp2); system("pause"); return 0; }

 

使用上面的代碼進行測試,就得到下面的結果,表明此算法是有效的。

 

 

 

  至此,雖然有點麻煩,但是問題也算解決了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM