關於Encoding.GetEncoding("utf-8")和Encoding.GetEncoding("GB2312")及Encoding.Default

最近處理bs的GridView導出word、excel的時候,經常出現亂碼的問題,一直很頭疼,折騰了兩天,從網上了找到了一些資料,其實說到底就是換成byte[]或者字節流最後輸入的問題。

根據多次在ie6、ie7上測試發現用Encoding.Default的基本可以解決95%的亂碼輸出的問題,雖然網上有帖子說“謹慎使用Encoding.Default”,原帖見備註一

但是另外的5%的亂碼需要Encoding.GetEncoding("utf-8")才能解決,至於Encoding.GetEncoding("GB2312")沒測試,因爲尚未發現Encoding.GetEncoding("utf-8")和Encoding.Default都無法解決的境況。UTF-8和GB2312的區別在備註二中。

這樣的話,治本的方法就是上傳或下載導出文件時,分析上傳文件字節流需要知道它的編碼,不過C#目前還沒有現成的函數能夠獲取,具體的方法可以參見備註三。

但是治本的方法也有缺點,1.對服務器的處理需求增大 2.修要修改的工作量增加。我這有個治標的辦法,就是讓user選擇編碼。雖然我極力不想這麼做,但是沒辦法,學費還是要交的,下一版的時候注意。

備註四中有Encoding.Default的講解。

 

 

備註一:from http://hi.baidu.com/irinihp/blog/item/d196cd35b4e7cb83a61e12aa.html

謹慎使用Encoding.Default

2009-03-30 15:36

 

在處理文本時,經常需要處理Encoding的概念。存在這個問題的原因在於,在.NET程序內文本只是string和char對象,而保存到硬盤時,或者準備用socket把它發送出去時,都得先轉換成byte[]或者字節流。而轉換的算法,就是Encoding。

老外的程序裏面,都喜歡用Encoding.ASCII,這是因爲他們主要只使用western字符;於是我們經常要在他們的代碼把Encoding.ASCII替換成Encoding.Default才能使用。

所謂Encoding.Default,是指當前系統設置的“默認字符集編碼方式”。你可以通過控制面板裏面的區域選項設置它(這是在我的機器上的設定):

查看圖片

注意紅色方框內的部分,“爲你的非Unicode程序選擇一個語言來匹配其本地語言版本”。這裏選擇了Chinese (PRC),則Encoding.Default等效於Encoding.GetEncoding("gb2312")。gb2312在代碼頁936,所以所有以雙字節編碼(ANSI編碼)字符的程序在這個系統上會使用936代碼頁,使用Unicode的不受影響。

你可以看到 Encoding.Default的值是和系統設定相關的。這樣,有些時候會出問題:一臺機器用socket發送一段文本到另一臺機器,兩邊都設定編碼方式爲Encoding.Default,但兩臺機器的區域選項的這個設置是不一樣的,卻沒有被注意;如果發送的是非western字符,則接受方必然會得到亂碼。這種問題往往會令人很困惑。

所以在面對中文且爲ANSI編碼的情況下,最好不要用Encoding.Default,而用 Encoding.GetEncoding("gb2312"),或者更直接的Encoding.GetEncoding(936);而如果有可能的話,最好全部使用unicode,比如utf-8,也就是Encoding.UTF8。有了unicode之後,其實我們不需要代碼頁的概念。

 

 

備註二:http://blog.csdn.net/forsiny/archive/2009/11/15/4813107.aspx

C#文本文件編碼問題,區別UTF-8和GB2312 收藏


最近用C#涉及到一些讀取 txt文本文件的操作,但是一個編碼問題就困惑了我好久。如果編碼選的不對,會造成亂碼。之前轉載的一片文章提出了一種解決方法,就是用new StreamReader(file, Encoding.Default)。這種方法解決了大部分問題,但是測試中發現對於有的UTF-8文件依然會造成亂碼(中文windows環境)。

於是上網搜索解決方案。大多數是說UTF-8有特殊的前導碼EF BB BF,只要認出這個就能判定是UTF-8編碼了。但是我測試的一個文件發現前面並

 沒有這些前導碼啊…於是繼續搜索……

先轉一篇直接知道怎麼做的博文:

http://blog.csdn.net/zdg/archive/2005/01/29/272643.aspx


--------------------------------------------------------------------------------

 一)需求
很多情況下我們需要知道字節流的編碼,比如
1) 使用編輯器打開文本文件的時候,編輯器需要識別文本文件的各種編碼
2) 上傳文件後,分析上傳文件字節流需要知道它的編碼

二)探討
不過C#目前還沒有現成的函數能夠獲取,經過和同事的探討,發現UTF8文件都有一個3字節的頭,爲“EF BB BF”(稱爲BOM--Byte Order Mark),判斷這個頭信息不就可以解決了嗎?代碼如下:

//判斷上傳的文件的編碼是否是UTF8,buff爲上傳文件的字節流
    enc     = Encoding.UTF8;
    testencbuff   = enc.GetPreamble();
    if(fileLength>testencbuff.Length && testencbuff[0] == buff[0] && testencbuff[1]==buff[1] && testencbuff[2]==buff[2])
    {
        // 是 UTF8編碼
        string buffString  = enc.GetString(buff);
    }
不過後來發現,不是所有的UTF8編碼的文件都有BOM信息,那如何解決呢?

三)最終的方案
沒有BOM信息只有通過逐個字節比較的方式才能解決。幸好已經有人解決這個問題了。推薦大家看:
http://dev.csdn.net/Develop/article/10/10961.shtm
http://dev.csdn.net/Develop/article/10/10962.shtm
這裏判斷所有的編碼,基本上都是通過字節比較的方式。java代碼很容易移植到.NET上,下面是UTF8判斷部分的C#代碼:

  int utf8_probability(byte[] rawtext)
  {
   int score = 0;
   int i, rawtextlen = 0;
   int goodbytes = 0, asciibytes = 0;

   // Maybe also use UTF8 Byte Order Mark:  EF BB BF

   // Check to see if characters fit into acceptable ranges
   rawtextlen = rawtext.Length;
   for (i = 0; i < rawtextlen; i++)
   {
    if ((rawtext[i] & (byte)0x7F) == rawtext[i])
    {  // One byte
     asciibytes++;
     // Ignore ASCII, can throw off count
    }
    else
    {
     int m_rawInt0 = Convert.ToInt16(rawtext[i]);
     int m_rawInt1 = Convert.ToInt16(rawtext[i+1]);
     int m_rawInt2 = Convert.ToInt16(rawtext[i+2]);

     if (256-64 <= m_rawInt0 && m_rawInt0 <= 256-33 && // Two bytes
      i+1 < rawtextlen &&
      256-128 <= m_rawInt1 && m_rawInt1 <= 256-65)
     {
      goodbytes += 2;
      i++;
     }
     else if (256-32 <= m_rawInt0 && m_rawInt0 <= 256-17 && // Three bytes
      i+2 < rawtextlen &&
      256-128 <= m_rawInt1 && m_rawInt1 <= 256-65 &&
      256-128 <= m_rawInt2 && m_rawInt2 <= 256-65)
     {
      goodbytes += 3;
      i+=2;
     }
    }
   }

   if (asciibytes == rawtextlen) { return 0; }

   score = (int)(100 * ((float)goodbytes/(float)(rawtextlen-asciibytes)));

   // If not above 98, reduce to zero to prevent coincidental matches
   // Allows for some (few) bad formed sequences
   if (score > 98)
   {
    return score;
   }
   else if (score > 95 && goodbytes > 30)
   {
    return score;
   }
   else
   {
    return 0;
   }

  }

 

--------------------------------------------------------------------------------


OK.利用上面的代碼,我來判斷一下是UTF-8編碼的概率:

 view plaincopy to clipboardprint?
Encoding encode;  
StreamReader srtest = new StreamReader(file.FullName,Encoding.Default);  
int p = utf8_probability(Encoding.Default.GetBytes(srtest.ReadToEnd()));  
if( p>80 )  
    encode = Encoding.GetEncoding(65001);//utf8  
else 
    encode = Encoding.Default;  
srtest.Close(); 
Encoding encode;
StreamReader srtest = new StreamReader(file.FullName,Encoding.Default);
int p = utf8_probability(Encoding.Default.GetBytes(srtest.ReadToEnd()));
if( p>80 )
    encode = Encoding.GetEncoding(65001);//utf8
else
    encode = Encoding.Default;
srtest.Close();

大功告成~哈哈~

感謝zdg的博文~

 

 

備註三:from http://blog.csdn.net/zdg/archive/2005/01/29/272643.aspx

字節流編碼獲取原來這麼複雜 收藏
一)需求
很多情況下我們需要知道字節流的編碼,比如
1) 使用編輯器打開文本文件的時候,編輯器需要識別文本文件的各種編碼
2) 上傳文件後,分析上傳文件字節流需要知道它的編碼

二)探討
不過C#目前還沒有現成的函數能夠獲取,經過和同事的探討,發現UTF8文件都有一個3字節的頭,爲“EF BB BF”(稱爲BOM--Byte Order Mark),判斷這個頭信息不就可以解決了嗎?代碼如下:

//判斷上傳的文件的編碼是否是UTF8,buff爲上傳文件的字節流
    enc     = Encoding.UTF8;
    testencbuff   = enc.GetPreamble();
    if(fileLength>testencbuff.Length && testencbuff[0] == buff[0] && testencbuff[1]==buff[1] && testencbuff[2]==buff[2])
    {
        // 是 UTF8編碼
        string buffString  = enc.GetString(buff);
    }
不過後來發現,不是所有的UTF8編碼的文件都有BOM信息,那如何解決呢?

三)最終的方案
沒有BOM信息只有通過逐個字節比較的方式才能解決。幸好已經有人解決這個問題了。推薦大家看:
http://dev.csdn.net/Develop/article/10/10961.shtm
http://dev.csdn.net/Develop/article/10/10962.shtm
這裏判斷所有的編碼,基本上都是通過字節比較的方式。java代碼很容易移植到.NET上,下面是UTF8判斷部分的C#代碼:

  int utf8_probability(byte[] rawtext)
  {
   int score = 0;
   int i, rawtextlen = 0;
   int goodbytes = 0, asciibytes = 0;

   // Maybe also use UTF8 Byte Order Mark:  EF BB BF

   // Check to see if characters fit into acceptable ranges
   rawtextlen = rawtext.Length;
   for (i = 0; i < rawtextlen; i++)
   {
    if ((rawtext[i] & (byte)0x7F) == rawtext[i])
    {  // One byte
     asciibytes++;
     // Ignore ASCII, can throw off count
    }
    else
    {
     int m_rawInt0 = Convert.ToInt16(rawtext[i]);
     int m_rawInt1 = Convert.ToInt16(rawtext[i+1]);
     int m_rawInt2 = Convert.ToInt16(rawtext[i+2]);

     if (256-64 <= m_rawInt0 && m_rawInt0 <= 256-33 && // Two bytes
      i+1 < rawtextlen &&
      256-128 <= m_rawInt1 && m_rawInt1 <= 256-65)
     {
      goodbytes += 2;
      i++;
     }
     else if (256-32 <= m_rawInt0 && m_rawInt0 <= 256-17 && // Three bytes
      i+2 < rawtextlen &&
      256-128 <= m_rawInt1 && m_rawInt1 <= 256-65 &&
      256-128 <= m_rawInt2 && m_rawInt2 <= 256-65)
     {
      goodbytes += 3;
      i+=2;
     }
    }
   }

   if (asciibytes == rawtextlen) { return 0; }

   score = (int)(100 * ((float)goodbytes/(float)(rawtextlen-asciibytes)));

   // If not above 98, reduce to zero to prevent coincidental matches
   // Allows for some (few) bad formed sequences
   if (score > 98)
   {
    return score;
   }
   else if (score > 95 && goodbytes > 30)
   {
    return score;
   }
   else
   {
    return 0;
   }

  }

參考資料:
字符檢測程序(上) 檢測GB2312、BIG5...    
http://dev.csdn.net/Develop/article/10/article/10/10961.shtm
Hello Unicode ——JAVA的中文處理學習筆記
http://www.chedong.com/tech/hello_unicode.html

 

 

備註四: from http://m.cnblogs.com/3108/768062.html

今天解決了一個棘手的問題。
在Mono下面,StreamReader用Encoding.Default竟然無法正常讀取GBK編碼的文件。

隨後展開了調查,用GBK編碼的源代碼中文Hello world程序竟然輸出亂碼!
用UTF-8編碼的源代碼文件編譯的程序就是正確的。

難道真都是Mono對編碼支持很混亂的原因嗎?

不!很多.NET程序員把Encoding.Default理解錯了。因爲在Windows平臺上Encoding.Default確實等於“GB18030”也就是GBK。

但是,隨着環境的不同,Encoding.Default也會改變!比如在WinCE或者是一部分Linux,Unix上,默認的編碼就是UTF-8,
這時候,Encoding.Default就相當於是Encoding.UTF8!

那要如何在默認是UTF-8的平臺讀取GBK編碼的文件呢?很簡單,用Encoding.GetEncoding("GBK")就可以了,GBK兼容GB2312。

以上是個人拙見,歡迎批評指正。

PS:
Oh,yeah yeah,我知道有些人會說,如果在Windows上就沒有這樣的煩心事,Mono的C# complier應該自動處理源文件編碼的問題(針對
於關於非默認編碼的源文件的問題)。很不幸的告訴你,.NET Framework自帶的C#編譯器同樣不能正確處理非默認編碼的源文件,比如
用Western什麼什麼的編碼的源文件,特殊字符甚至會導致編譯失敗。但是爲什麼在Windows下,UTF-8編碼的源文件就可以正確處理呢(
在UTF-8不是操作系統默認的編碼的情況下)?說實話,that's a little tricky,因爲絕大多數的UTF-8的編碼都是帶有BOM的,很多人應
該還對VS2003中ASP.NET使用UTF-8編碼的源文件造成瀏覽時候變成亂碼記憶猶新吧,那就是因爲VS2003默認的UTF-8編碼是沒有BOM的!
導致C#編譯起編譯ASP.NET頁面的時候錯誤的使用了默認的GBK的編碼,導致了最終頁面的亂碼現象。好在現在VS2005默認的UTF-8都也已
經是UTF-8 with signature了。哦,BOM的全稱是Byte Ordered Mask,目的是爲了區別Unicode big或者small endian的,後來被一些Geek
用來區別是不是UTF-8了,當然,這樣做有利也有弊,總的來說,不夠elegant。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章