1. 程式人生 > >Java中文編碼及各種編碼互轉和Java判斷檔案編碼

Java中文編碼及各種編碼互轉和Java判斷檔案編碼

Unicode UTF-8 GBK 及一點Java程式碼

Unicode UTF-8 GBK這些不同的編碼,我們可以想象為不同的字典。同一個漢字,在不同的字典裡面,我們用不同的編號儲存。比如漢字"陳"在Unicode裡編號為9648,在GBK裡面是0xB3C2,在UTF-8中呢就是0xE99988. 大家可以通過"附件->系統工具->編碼對映表"查詢" 陳"這個字的編碼,可以得到它的Unicode編碼及GBK的編碼。UTF-8的生成依賴於對應字元的Unicode編碼。規則如下:

Unicode

UTF-8

說明

0000 - 007F

0xxxxxxx

對於這個範圍的Unicode,UTF-8編碼也用一個位元組來表示

0080 - 07FF

110xxxxx 10xxxxxx

對於這個範圍的Unicode,UTF-8編碼也用兩個位元組來表示。在第一個位元組前加上110,在後一個節里加上10

0800 - FFFF

1110xxxx 10xxxxxx 10xxxxxx

對於這個範圍的Unicode,UTF-8編碼也用兩個位元組來表示。在第一個位元組前加上110,在後兩個節里加上10

"陳"字落在第三個區間,它的utf-8編碼需要用三個位元組表示,具體轉換過程如下:

① 9648的二進位制編碼如下 1001011001001000

② 將它分成4-6-6三段分別是 1001 011001 001000

③ 分別在前面加上1110 10 10 變成如下:1110

1001 10011001 10001000

在Java當中我們可以通過如下方法輸出"陳"這個字:

1) System.out.println("/u9648");

2) byte[] bs = newbyte[]{(byte)179,(byte)194};

System.out.println(new String(bs,"GBK"));

注:179是B3的十進位制,194是C2的十進位制

3) byte[] bs = newbyte[]{(byte)233,(byte)153,(byte)136};

System.out.println(new String(bs,"utf-8"));

注:數組裡的值就是上面utf-8

二制值對應的十進位制值

233 - 11101001,   153 - 10011001,  136 - 10001000 

反過來我們看下面的程式碼:

System.out.println("陳".getBytes("GBK").length);

System.out.println("陳".getBytes("UTF-8").length);

打印出來的值分別是2,3

關於編碼的很有意思的是例子就是在一個新建的文字檔案裡面輸入"聯通"兩個字,結果卻不能正確顯示。這是因為預設系統文字檔案用的是GBK編碼輸入,而聯通的GBK編碼如下:

文字

十六進位制

二進位制

說明

0xC1AA

11000001 10101010

由於它的兩個位元組由110 和10開頭,所以雙擊開啟的時候,把內容的GBK編碼認成了UTF-8編碼,所以把它當成 00001 101010 這個Unicode編碼所程式碼的文字,這個字元的Unicode值為 /u006A,它就是字元J

0xCDA8

11001101 10101000

這個轉換成UTF-8後,沒有對應的字元存在,所以最終文件沒辦法正確顯示

GBK中文與UTF-8字元與轉例子

import java.io.UnsupportedEncodingException;

/*
 * GBK中文與UTF-8字元與轉例子
 */
public class CharacterConvert {
 public static void main(String[] args) {
  String utf8 = gbkToUtf8("陳");
  System.out.println(utf8);

  System.out.println(utf8Togbk(utf8));
 }

 /*
  * 中文字串轉換為UTF-8字串輸出
  */
 public static String gbkToUtf8(String str) {
  StringBuffer sb = new StringBuffer("");
  for (int i = 0, size = str.length(); i < size; i++) {
   String s = str.substring(i, i + 1);
   try {
    byte[] bytes = s.getBytes("UTF-8");
    if (bytes.length == 3) {
     // 表示是中文,中文在UTF-8中佔三個位元組
     sb.append(gbkToUtf8(bytes));
    } else {
     sb.append(s);
    }
   } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
   }
  }

  return sb.toString();
 }

 private static String gbkToUtf8(byte[] bytes) {
  StringBuffer sb = new StringBuffer();

  String s = Integer.toBinaryString(bytes[0]);
  s = s.substring(s.length() - 8);// 得到是32位的二進位制字串,只取後8位
  sb.append(s.substring(s.length() - 4));// 去掉前4位

  s = Integer.toBinaryString(bytes[1]);
  s = s.substring(s.length() - 8);// 得到是32位的二進位制字串,只取後8位
  sb.append(s.substring(s.length() - 6));// 去掉前2位

  s = Integer.toBinaryString(bytes[2]);
  s = s.substring(s.length() - 8);// 得到是32位的二進位制字串,只取後8位
  sb.append(s.substring(s.length() - 6));// 去掉前2位

  String utf8 = "\\u"
    + Integer.toHexString(Integer.valueOf(sb.toString(), 2));

  return utf8;
 }

 /*
  * UTF-8字串轉換為中文字串輸出 \\u開頭及後4位是對應的中文
  */
 public static String utf8Togbk(String utf8) {
  int length = utf8.length();
  int index = 0;
  StringBuffer sb = new StringBuffer();
  while (index < length) {
   if (length - index < 2) {
    sb.append(utf8.charAt(index));
    index++;
   } else if ("\\u".equals(utf8.substring(index, index + 2))) {
    index += 2;
    String s = utf8.substring(index, index + 4);
    char ch = (char) Integer.parseInt(s, 16);
    try {
     s = new String((ch + "").getBytes("gbk"), "gbk");
     sb.append(s);
     index += 4;
    } catch (UnsupportedEncodingException e) {
     e.printStackTrace();
    }

   } else {
    sb.append(utf8.charAt(index));
    index++;
   }

  }

  return sb.toString();
 }

}

Java 中的中文編碼問題

不知道大家有沒有想過一個問題,那就是為什麼要編碼?我們能不能不編碼?要回答這個問題必須要回到計算機是如何表示我們人類能夠理解的符號的,這些符號也就是我們人類使用的語言。由於人類的語言有太多,因而表示這些語言的符號太多,無法用計算機中一個基本的儲存單元—— byte 來表示,因而必須要經過拆分或一些翻譯工作,才能讓計算機能理解。我們可以把計算機能夠理解的語言假定為英語,其它語言要能夠在計算機中使用必須經過一次翻譯,把它翻譯成英語。這個翻譯的過程就是編碼。所以可以想象只要不是說英語的國家要能夠使用計算機就必須要經過編碼。這看起來有些霸道,但是這就是現狀,這也和我們國家現在在大力推廣漢語一樣,希望其它國家都會說漢語,以後其它的語言都翻譯成漢語,我們可以把計算機中儲存資訊的最小單位改成漢字,這樣我們就不存在編碼問題了。

所以總的來說,編碼的原因可以總結為:

  1. 計算機中儲存資訊的最小單元是一個位元組即 8 個 bit,所以能表示的字元範圍是 0~255 個
  2. 人類要表示的符號太多,無法用一個位元組來完全表示
  3. 要解決這個矛盾必須需要一個新的資料結構 char,從 char 到 byte 必須編碼

明白了各種語言需要交流,經過翻譯是必要的,那又如何來翻譯呢?計算中提拱了多種翻譯方式,常見的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它們都可以被看作為字典,它們規定了轉化的規則,按照這個規則就可以讓計算機正確的表示我們的字元。目前的編碼格式很多,例如 GB2312、GBK、UTF-8、UTF-16 這幾種格式都可以表示一個漢字,那我們到底選擇哪種編碼格式來儲存漢字呢?這就要考慮到其它因素了,是儲存空間重要還是編碼的效率重要。根據這些因素來正確選擇編碼格式,下面簡要介紹一下這幾種編碼格式。

  • ASCII 碼

學過計算機的人都知道 ASCII 碼,總共有 128 個,用一個位元組的低 7 位表示,0~31 是控制字元如換行回車刪除等;32~126 是列印字元,可以通過鍵盤輸入並且能夠顯示出來。

  • ISO-8859-1

128 個字元顯然是不夠用的,於是 ISO 組織在 ASCII 碼基礎上又制定了一些列標準用來擴充套件 ASCII 編碼,它們是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵蓋了大多數西歐語言字元,所有應用的最廣泛。ISO-8859-1 仍然是單位元組編碼,它總共能表示 256 個字元。

  • GB2312

它的全稱是《資訊交換用漢字編碼字符集 基本集》,它是雙位元組編碼,總的編碼範圍是 A1-F7,其中從 A1-A9 是符號區,總共包含 682 個符號,從 B0-F7 是漢字區,包含 6763 個漢字。

  • GBK

全稱叫《漢字內碼擴充套件規範》,是國家技術監督局為 windows95 所制定的新的漢字內碼規範,它的出現是為了擴充套件 GB2312,加入更多的漢字,它的編碼範圍是 8140~FEFE(去掉 XX7F)總共有 23940 個碼位,它能表示 21003 個漢字,它的編碼是和 GB2312 相容的,也就是說用 GB2312 編碼的漢字可以用 GBK 來解碼,並且不會有亂碼。

  • GB18030

全稱是《資訊交換用漢字編碼字符集》,是我國的強制標準,它可能是單位元組、雙位元組或者四位元組編碼,它的編碼與 GB2312 編碼相容,這個雖然是國家標準,但是實際應用系統中使用的並不廣泛。

  • UTF-16

說到 UTF 必須要提到 Unicode(Universal Code 統一碼),ISO 試圖想建立一個全新的超語言字典,世界上所有的語言都可以通過這本字典來相互翻譯。可想而知這個字典是多麼的複雜,關於 Unicode 的詳細規範可以參考相應文件。Unicode 是 Java 和 XML 的基礎,下面詳細介紹 Unicode 在計算機中的儲存形式。

UTF-16 具體定義了 Unicode 字元在計算機中存取方法。UTF-16 用兩個位元組來表示 Unicode 轉化格式,這個是定長的表示方法,不論什麼字元都可以用兩個位元組表示,兩個位元組是 16 個 bit,所以叫 UTF-16。UTF-16 表示字元非常方便,每兩個位元組表示一個字元,這個在字串操作時就大大簡化了操作,這也是 Java 以 UTF-16 作為記憶體的字元儲存格式的一個很重要的原因。

  • UTF-8

UTF-16 統一採用兩個位元組表示一個字元,雖然在表示上非常簡單方便,但是也有其缺點,有很大一部分字元用一個位元組就可以表示的現在要兩個位元組表示,儲存空間放大了一倍,在現在的網路頻寬還非常有限的今天,這樣會增大網路傳輸的流量,而且也沒必要。而 UTF-8 採用了一種變長技術,每個編碼區域有不同的字碼長度。不同型別的字元可以是由 1~6 個位元組組成。

UTF-8 有以下編碼規則:

  1. 如果一個位元組,最高位(第 8 位)為 0,表示這是一個 ASCII 字元(00 - 7F)。可見,所有 ASCII 編碼已經是 UTF-8 了。
  2. 如果一個位元組,以 11 開頭,連續的 1 的個數暗示這個字元的位元組數,例如:110xxxxx 代表它是雙位元組 UTF-8 字元的首位元組。
  3. 如果一個位元組,以 10 開始,表示它不是首位元組,需要向前查詢才能得到當前字元的首位元組

前面描述了常見的幾種編碼格式,下面將介紹 Java 中如何處理對編碼的支援,什麼場合中需要編碼。

我們知道涉及到編碼的地方一般都在字元到位元組或者位元組到字元的轉換上,而需要這種轉換的場景主要是在 I/O 的時候,這個 I/O 包括磁碟 I/O 和網路 I/O,關於網路 I/O 部分在後面將主要以 Web 應用為例介紹。下圖是 Java 中處理 I/O 問題的介面:


Figure xxx. Requires a heading

Reader 類是 Java 的 I/O 中讀字元的父類,而 InputStream 類是讀位元組的父類,InputStreamReader 類就是關聯位元組到字元的橋樑,它負責在 I/O 過程中處理讀取位元組到字元的轉換,而具體位元組到字元的解碼實現它由 StreamDecoder 去實現,在 StreamDecoder 解碼過程中必須由使用者指定 Charset 編碼格式。值得注意的是如果你沒有指定 Charset,將使用本地環境中的預設字符集,例如在中文環境中將使用 GBK 編碼。

寫的情況也是類似,字元的父類是 Writer,位元組的父類是 OutputStream,通過 OutputStreamWriter 轉換字元到位元組。如下圖所示:


Figure xxx. Requires a heading

同樣 StreamEncoder 類負責將字元編碼成位元組,編碼格式和預設編碼規則與解碼是一致的。

如下面一段程式碼,實現了檔案的讀寫功能:

				 
 String file = "c:/stream.txt"; 
 String charset = "UTF-8"; 
 // 寫字元換轉成位元組流
 FileOutputStream outputStream = new FileOutputStream(file); 
 OutputStreamWriter writer = new OutputStreamWriter( 
 outputStream, charset); 
 try { 
    writer.write("這是要儲存的中文字元"); 
 } finally { 
    writer.close(); 
 } 
 // 讀取位元組轉換成字元
 FileInputStream inputStream = new FileInputStream(file); 
 InputStreamReader reader = new InputStreamReader( 
 inputStream, charset); 
 StringBuffer buffer = new StringBuffer(); 
 char[] buf = new char[64]; 
 int count = 0; 
 try { 
    while ((count = reader.read(buf)) != -1) { 
        buffer.append(buffer, 0, count); 
    } 
 } finally { 
    reader.close(); 
 } 

在我們的應用程式中涉及到 I/O 操作時只要注意指定統一的編解碼 Charset 字符集,一般不會出現亂碼問題,有些應用程式如果不注意指定字元編碼,中文環境中取作業系統預設編碼,如果編解碼都在中文環境中,通常也沒問題,但是還是強烈的不建議使用作業系統的預設編碼,因為這樣,你的應用程式的編碼格式就和執行環境繫結起來了,在跨環境下很可能出現亂碼問題。

在 Java 開發中除了 I/O 涉及到編碼外,最常用的應該就是在記憶體中進行字元到位元組的資料型別的轉換,Java 中用 String 表示字串,所以 String 類就提供轉換到位元組的方法,也支援將位元組轉換為字串的建構函式。如下程式碼示例:

 String s = "這是一段中文字串"; 
 byte[] b = s.getBytes("UTF-8"); 
 String n = new String(b,"UTF-8"); 

另外一個是已經被被廢棄的 ByteToCharConverter 和 CharToByteConverter 類,它們分別提供了 convertAll 方法可以實現 byte[] 和 char[] 的互轉。如下程式碼所示:

 ByteToCharConverter charConverter = ByteToCharConverter.getConverter("UTF-8"); 
 char c[] = charConverter.convertAll(byteArray); 
 CharToByteConverter byteConverter = CharToByteConverter.getConverter("UTF-8"); 
 byte[] b = byteConverter.convertAll(c); 

這兩個類已經被 Charset 類取代,Charset 提供 encode 與 decode 分別對應 char[] 到 byte[] 的編碼和 byte[] 到 char[] 的解碼。如下程式碼所示:

 Charset charset = Charset.forName("UTF-8"); 
 ByteBuffer byteBuffer = charset.encode(string); 
 CharBuffer charBuffer = charset.decode(byteBuffer); 

編碼與解碼都在一個類中完成,通過 forName 設定編解碼字符集,這樣更容易統一編碼格式,比 ByteToCharConverter 和 CharToByteConverter 類更方便。

Java 中還有一個 ByteBuffer 類,它提供一種 char 和 byte 之間的軟轉換,它們之間轉換不需要編碼與解碼,只是把一個 16bit 的 char 格式,拆分成為 2 個 8bit 的 byte 表示,它們的實際值並沒有被修改,僅僅是資料的型別做了轉換。如下程式碼所以:

 ByteBuffer heapByteBuffer = ByteBuffer.allocate(1024); 
 ByteBuffer byteBuffer = heapByteBuffer.putChar(c); 

以上這些提供字元和位元組之間的相互轉換隻要我們設定編解碼格式統一一般都不會出現問題。

前面介紹了幾種常見的編碼格式,這裡將以實際例子介紹 Java 中如何實現編碼及解碼,下面我們以“I am 君山”這個字串為例介紹 Java 中如何把它以 ISO-8859-1、GB2312、GBK、UTF-16、UTF-8 編碼格式進行編碼的。

				 
 public static void encode() { 
        String name = "I am 君山"; 
        toHex(name.toCharArray()); 
        try { 
            byte[] iso8859 = name.getBytes("ISO-8859-1"); 
            toHex(iso8859); 
            byte[] gb2312 = name.getBytes("GB2312"); 
            toHex(gb2312); 
            byte[] gbk = name.getBytes("GBK"); 
            toHex(gbk); 
            byte[] utf16 = name.getBytes("UTF-16"); 
            toHex(utf16); 
            byte[] utf8 = name.getBytes("UTF-8"); 
            toHex(utf8); 
        } catch (UnsupportedEncodingException e) { 
            e.printStackTrace(); 
        } 
 } 

我們把 name 字串按照前面說的幾種編碼格式進行編碼轉化成 byte 陣列,然後以 16 進位制輸出,我們先看一下 Java 是如何進行編碼的。

下面是 Java 中編碼需要用到的類圖

首先根據指定的 charsetName 通過 Charset.forName(charsetName) 設定 Charset 類,然後根據 Charset 建立 CharsetEncoder 物件,再呼叫 CharsetEncoder.encode 對字串進行編碼,不同的編碼型別都會對應到一個類中,實際的編碼過程是在這些類中完成的。下面是 String. getBytes(charsetName) 編碼過程的時序圖

從上圖可以看出根據 charsetName 找到 Charset 類,然後根據這個字符集編碼生成 CharsetEncoder,這個類是所有字元編碼的父類,針對不同的字元編碼集在其子類中定義瞭如何實現編碼,有了 CharsetEncoder 物件後就可以呼叫 encode 方法去實現編碼了。這個是 String.getBytes 編碼方法,其它的如 StreamEncoder 中也是類似的方式。下面看看不同的字符集是如何將前面的字串編碼成 byte 陣列的?

如字串“I am 君山”的 char 陣列為 49 20 61 6d 20 541b 5c71,下面把它按照不同的編碼格式轉化成相應的位元組。

字串“I am 君山”用 ISO-8859-1 編碼,下面是編碼結果:


Figure xxx. Requires a heading

從上圖看出 7 個 char 字元經過 ISO-8859-1 編碼轉變成 7 個 byte 陣列,ISO-8859-1 是單位元組編碼,中文“君山”被轉化成值是 3f 的 byte。3f 也就是“?”字元,所以經常會出現中文變成“?”很可能就是錯誤的使用了 ISO-8859-1 這個編碼導致的。中文字元經過 ISO-8859-1 編碼會丟失資訊,通常我們稱之為“黑洞”,它會把不認識的字元吸收掉。由於現在大部分基礎的 Java 框架或系統預設的字符集編碼都是 ISO-8859-1,所以很容易出現亂碼問題,後面將會分析不同的亂碼形式是怎麼出現的。

字串“I am 君山”用 GB2312 編碼,下面是編碼結果:


Figure xxx. Requires a heading

GB2312 對應的 Charset 是 sun.nio.cs.ext. EUC_CN 而對應的 CharsetDecoder 編碼類是 sun.nio.cs.ext. DoubleByte,GB2312 字符集有一個 char 到 byte 的碼錶,不同的字元編碼就是查這個碼錶找到與每個字元的對應的位元組,然後拼裝成 byte 陣列。查表的規則如下:

 c2b[c2bIndex[char >> 8] + (char & 0xff)] 

如果查到的碼位值大於 oxff 則是雙位元組,否則是單位元組。雙位元組高 8 位作為第一個位元組,低 8 位作為第二個位元組,如下程式碼所示:

 if (bb > 0xff) {    // DoubleByte 
            if (dl - dp < 2) 
                return CoderResult.OVERFLOW; 
            da[dp++] = (byte) (bb >> 8); 
            da[dp++] = (byte) bb; 
 } else {                      // SingleByte 
            if (dl - dp < 1) 
                return CoderResult.OVERFLOW; 
            da[dp++] = (byte) bb; 
 } 

從上圖可以看出前 5 個字元經過編碼後仍然是 5 個位元組,而漢字被編碼成雙位元組,在第一節中介紹到 GB2312 只支援 6763 個漢字,所以並不是所有漢字都能夠用 GB2312 編碼。

字串“I am 君山”用 GBK 編碼,下面是編碼結果:


Figure xxx. Requires a heading

你可能已經發現上圖與 GB2312 編碼的結果是一樣的,沒錯 GBK 與 GB2312 編碼結果是一樣的,由此可以得出 GBK 編碼是相容 GB2312 編碼的,它們的編碼演算法也是一樣的。不同的是它們的碼錶長度不一樣,GBK 包含的漢字字元更多。所以只要是經過 GB2312 編碼的漢字都可以用 GBK 進行解碼,反過來則不然。

字串“I am 君山”用 UTF-16 編碼,下面是編碼結果:


Figure xxx. Requires a heading

用 UTF-16 編碼將 char 陣列放大了一倍,單位元組範圍內的字元,在高位補 0 變成兩個位元組,中文字元也變成兩個位元組。從 UTF-16 編碼規則來看,僅僅將字元的高位和地位進行拆分變成兩個位元組。特點是編碼效率非常高,規則很簡單,由於不同處理器對 2 位元組處理方式不同,Big-endian(高位位元組在前,低位位元組在後)或 Little-endian(低位位元組在前,高位位元組在後)編碼,所以在對一串字串進行編碼是需要指明到底是 Big-endian 還是 Little-endian,所以前面有兩個位元組用來儲存 BYTE_ORDER_MARK 值,UTF-16 是用定長 16 位(2 位元組)來表示的 UCS-2 或 Unicode 轉換格式,通過代理對來訪問 BMP 之外的字元編碼。

字串“I am 君山”用 UTF-8 編碼,下面是編碼結果:


Figure xxx. Requires a heading

UTF-16 雖然編碼效率很高,但是對單位元組範圍內字元也放大了一倍,這無形也浪費了儲存空間,另外 UTF-16 採用順序編碼,不能對單個字元的編碼值進行校驗,如果中間的一個字元碼值損壞,後面的所有碼值都將受影響。而 UTF-8 這些問題都不存在,UTF-8 對單位元組範圍內字元仍然用一個位元組表示,對漢字採用三個位元組表示。它的編碼規則如下:

				 
 private CoderResult encodeArrayLoop(CharBuffer src, 
 ByteBuffer dst){ 
            char[] sa = src.array(); 
            int sp = src.arrayOffset() + src.position(); 
            int sl = src.arrayOffset() + src.limit(); 
            byte[] da = dst.array(); 
            int dp = dst.arrayOffset() + dst.position(); 
            int dl = dst.arrayOffset() + dst.limit(); 
            int dlASCII = dp + Math.min(sl - sp, dl - dp); 
            // ASCII only loop 
            while (dp < dlASCII && sa[sp] < '\u0080') 
                da[dp++] = (byte) sa[sp++]; 
            while (sp < sl) { 
                char c = sa[sp]; 
                if (c < 0x80) { 
                    // Have at most seven bits 
                    if (dp >= dl) 
                        return overflow(src, sp, dst, dp); 
                    da[dp++] = (byte)c; 
                } else if (c < 0x800) { 
                    // 2 bytes, 11 bits 
                    if (dl - dp < 2) 
                        return overflow(src, sp, dst, dp); 
                    da[dp++] = (byte)(0xc0 | (c >> 6)); 
                    da[dp++] = (byte)(0x80 | (c & 0x3f)); 
                } else if (Character.isSurrogate(c)) { 
                    // Have a surrogate pair 
                    if (sgp == null) 
                        sgp = new Surrogate.Parser(); 
                    int uc = sgp.parse(c, sa, sp, sl); 
                    if (uc < 0) { 
                        updatePositions(src, sp, dst, dp); 
                        return sgp.error(); 
                    } 
                    if (dl - dp < 4) 
                        return overflow(src, sp, dst, dp); 
                    da[dp++] = (byte)(0xf0 | ((uc >> 18))); 
                    da[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f)); 
                    da[dp++] = (byte)(0x80 | ((uc >>  6) & 0x3f)); 
                    da[dp++] = (byte)(0x80 | (uc & 0x3f)); 
                    sp++;  // 2 chars 
                } else { 
                    // 3 bytes, 16 bits 
                    if (dl - dp < 3) 
                        return overflow(src, sp, dst, dp); 
                    da[dp++] = (byte)(0xe0 | ((c >> 12))); 
                    da[dp++] = (byte)(0x80 | ((c >>  6) & 0x3f)); 
                    da[dp++] = (byte)(0x80 | (c & 0x3f)); 
                } 
                sp++; 
            } 
            updatePositions(src, sp, dst, dp); 
            return CoderResult.UNDERFLOW; 
 } 

UTF-8 編碼與 GBK 和 GB2312 不同,不用查碼錶,所以在編碼效率上 UTF-8 的效率會更好,所以在儲存中文字元時 UTF-8 編碼比較理想。

對中文字元後面四種編碼格式都能處理,GB2312 與 GBK 編碼規則類似,但是 GBK 範圍更大,它能處理所有漢字字元,所以 GB2312 與 GBK 比較應該選擇 GBK。UTF-16 與 UTF-8 都是處理 Unicode 編碼,它們的編碼規則不太相同,相對來說 UTF-16 編碼效率最高,字元到位元組相互轉換更簡單,進行字串操作也更好。它適合在本地磁碟和記憶體之間使用,可以進行字元和位元組之間快速切換,如 Java 的記憶體編碼就是採用 UTF-16 編碼。但是它不適合在網路之間傳輸,因為網路傳輸容易損壞位元組流,一旦位元組流損壞將很難恢復,想比較而言 UTF-8 更適合網路傳輸,對 ASCII 字符采用單位元組儲存,另外單個字元損壞也不會影響後面其它字元,在編碼效率上介於 GBK 和 UTF-16 之間,所以 UTF-8 在編碼效率上和編碼安全性上做了平衡,是理想的中文編碼方式。

對於使用中文來說,有 I/O 的地方就會涉及到編碼,前面已經提到了 I/O 操作會引起編碼,而大部分 I/O 引起的亂碼都是網路 I/O,因為現在幾乎所有的應用程式都涉及到網路操作,而資料經過網路傳輸都是以位元組為單位的,所以所有的資料都必須能夠被序列化為位元組。在 Java 中資料被序列化必須繼承 Serializable 介面。

這裡有一個問題,你是否認真考慮過一段文字它的實際大小應該怎麼計算,我曾經碰到過一個問題:就是要想辦法壓縮 Cookie 大小,減少網路傳輸量,當時有選擇不同的壓縮演算法,發現壓縮後字元數是減少了,但是並沒有減少位元組數。所謂的壓縮只是將多個單位元組字元通過編碼轉變成一個多位元組字元。減少的是 String.length(),而並沒有減少最終的位元組數。例如將“ab”兩個字元通過某種編碼轉變成一個奇怪的字元,雖然字元數從兩個變成一個,但是如果採用 UTF-8 編碼這個奇怪的字元最後經過編碼可能又會變成三個或更多的位元組。同樣的道理比如整型數字 1234567 如果當成字元來儲存,採用 UTF-8 來編碼佔用 7 個 byte,採用 UTF-16 編碼將會佔用 14 個 byte,但是把它當成 int 型數字來儲存只需要 4 個 byte 來儲存。所以看一段文字的大小,看字元本身的長度是沒有意義的,即使是一樣的字符采用不同的編碼最終儲存的大小也會不同,所以從字元到位元組一定要看編碼型別。

另外一個問題,你是否考慮過,當我們在電腦中某個文字編輯器裡輸入某個漢字時,它到底是怎麼表示的?我們知道,計算機裡所有的資訊都是以 01 表示的,那麼一個漢字,它到底是多少個 0 和 1 呢?我們能夠看到的漢字都是以字元形式出現的,例如在 Java 中“淘寶”兩個字元,它在計算機中的數值 10 進位制是 28120 和 23453,16 進位制是 6bd8 和 5d9d,也就是這兩個字元是由這兩個數字唯一表示的。Java 中一個 char 是 16 個 bit 相當於兩個位元組,所以兩個漢字用 char 表示在記憶體中佔用相當於四個位元組的空間。

這兩個問題搞清楚後,我們看一下 Java Web 中那些地方可能會存在編碼轉換?

使用者從瀏覽器端發起一個 HTTP 請求,需要存在編碼的地方是 URL、Cookie、Parameter。伺服器端接受到 HTTP 請求後要解析 HTTP 協議,其中 URI、Cookie 和 POST 表單引數需要解碼,伺服器端可能還需要讀取資料庫中的資料,本地或網路中其它地方的文字檔案,這些資料都可能存在編碼問題,當 Servlet 處理完所有請求的資料後,需要將這些資料再編碼通過 Socket 傳送到使用者請求的瀏覽器裡,再經過瀏覽器解碼成為文字。這些過程如下圖所示:

如上圖所示一次 HTTP 請求設計到很多地方需要編解碼,它們編解碼的規則是什麼?下面將會重點闡述一下:

使用者提交一個 URL,這個 URL 中可能存在中文,因此需要編碼,如何對這個 URL 進行編碼?根據什麼規則來編碼?有如何來解碼?如下圖一個 URL:

上圖中以 Tomcat 作為 Servlet Engine 為例,它們分別對應到下面這些配置檔案中:

Port 對應在 Tomcat 的 <Connector port="8080"/> 中配置,而 Context Path 在 <Context path="/examples"/> 中配置,Servlet Path 在 Web 應用的 web.xml 中的

 <servlet-mapping> 
        <servlet-name>junshanExample</servlet-name> 
        <url-pattern>/servlets/servlet/*</url-pattern> 
 </servlet-mapping> 

<url-pattern> 中配置,PathInfo 是我們請求的具體的 Servlet,QueryString 是要傳遞的引數,注意這裡是在瀏覽器裡直接輸入 URL 所以是通過 Get 方法請求的,如果是 POST 方法請求的話,QueryString 將通過表單方式提交到伺服器端,這個將在後面再介紹。

上圖中 PathInfo 和 QueryString 出現了中文,當我們在瀏覽器中直接輸入這個 URL 時,在瀏覽器端和服務端會如何編碼和解析這個 URL 呢?為了驗證瀏覽器是怎麼編碼 URL 的我們選擇 FireFox 瀏覽器並通過 HTTPFox 外掛觀察我們請求的 URL 的實際的內容,以下是 URL:HTTP://localhost:8080/examples/servlets/servlet/ 君山 ?author= 君山在中文 FireFox3.6.12 的測試結果

君山的編碼結果分別是:e5 90 9b e5 b1 b1,be fd c9 bd,查閱上一屆的編碼可知,PathInfo 是 UTF-8 編碼而 QueryString 是經過 GBK 編碼,至於為什麼會有“%”?查閱 URL 的編碼規範 RFC3986 可知瀏覽器編碼 URL 是將非 ASCII 字元按照某種編碼格式編碼成 16 進位制數字然後將每個 16 進製表示的位元組前加上“%”,所以最終的 URL 就成了上圖的格式了。

預設情況下中文 IE 最終的編碼結果也是一樣的,不過 IE 瀏覽器可以修改 URL 的編碼格式在選項 -> 高階 -> 國際裡面的傳送 UTF-8 URL 選項可以取消。

從上面測試結果可知瀏覽器對 PathInfo 和 QueryString 的編碼是不一樣的,不同瀏覽器對 PathInfo 也可能不一樣,這就對伺服器的解碼造成很大的困難,下面我們以 Tomcat 為例看一下,Tomcat 接受到這個 URL 是如何解碼的。

解析請求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,這個方法把傳過來的 URL 的 byte[] 設定到 org.apache.coyote.Request 的相應的屬性中。這裡的 URL 仍然是 byte 格式,轉成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:

 protected void convertURI(MessageBytes uri, Request request) 
 throws Exception { 
        ByteChunk bc = uri.getByteChunk(); 
        int length = bc.getLength(); 
        CharChunk cc = uri.getCharChunk(); 
        cc.allocate(length, -1); 
        String enc = connector.getURIEncoding(); 
        if (enc != null) { 
            B2CConverter conv = request.getURIConverter(); 
            try { 
                if (conv == null) { 
                    conv = new B2CConverter(enc); 
                    request.setURIConverter(conv); 
                } 
            } catch (IOException e) {...} 
            if (conv != null) { 
                try { 
                    conv.convert(bc, cc, cc.getBuffer().length - 
 cc.getEnd()); 
                    uri.setChars(cc.getBuffer(), cc.getStart(), 
 cc.getLength()); 
                    return; 
                } catch (IOException e) {...} 
            } 
        } 
        // Default encoding: fast conversion 
        byte[] bbuf = bc.getBuffer(); 
        char[] cbuf = cc.getBuffer(); 
        int start = bc.getStart(); 
        for (int i = 0; i < length; i++) { 
            cbuf[i] = (char) (bbuf[i + start] & 0xff); 
        } 
        uri.setChars(cbuf, 0, length); 
 } 

從上面的程式碼中可以知道對 URL 的 URI 部分進行解碼的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/> 中定義的,如果沒有定義,那麼將以預設編碼 ISO-8859-1 解析。所以如果有中文 URL 時最好把 URIEncoding 設定成 UTF-8 編碼。

QueryString 又如何解析? GET 方式 HTTP 請求的 QueryString 與 POST 方式 HTTP 請求的表單引數都是作為 Parameters 儲存,都是通過 request.getParameter 獲取引數值。對它們的解碼是在 request.getParameter 方法第一次被呼叫時進行的。request.getParameter 方法被呼叫時將會呼叫 org.apache.catalina.connector.Request 的 parseParameters 方法。這個方法將會對 GET 和 POST 方式傳遞的引數進行解碼,但是它們的解碼字符集有可能不一樣。POST 表單的解碼將在後面介紹,QueryString 的解碼字符集是在哪定義的呢?它本身是通過 HTTP 的 Header 傳到服務端的,並且也在 URL 中,是否和 URI 的解碼字符集一樣呢?從前面瀏覽器對 PathInfo 和 QueryString 的編碼採取不同的編碼格式不同可以猜測到解碼字符集肯定也不會是一致的。的確是這樣 QueryString 的解碼字符集要麼是 Header 中 ContentType 中定義的 Charset 要麼就是預設的 ISO-8859-1,要使用 ContentType 中定義的編碼就要設定 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 設定為 true。這個配置項的名字有點讓人產生混淆,它並不是對整個 URI 都採用 BodyEncoding 進行解碼而僅僅是對 QueryString 使用 BodyEncoding 解碼,這一點還要特別注意。

從上面的 URL 編碼和解碼過程來看,比較複雜,而且編碼和解碼並不是我們在應用程式中能完全控制的,所以在我們的應用程式中應該儘量避免在 URL 中使用非 ASCII 字元,不然很可能會碰到亂碼問題,當然在我們的伺服器端最好設定 <Connector/> 中的 URIEncoding 和 useBodyEncodingForURI 兩個引數。

當客戶端發起一個 HTTP 請求除了上面的 URL 外還可能會在 Header 中傳遞其它引數如 Cookie、redirectPath 等,這些使用者設定的值很可能也會存在編碼問題,Tomcat 對它們又是怎麼解碼的呢?

對 Header 中的項進行解碼也是在呼叫 request.getHeader 是進行的,如果請求的 Header 項沒有解碼則呼叫 MessageBytes 的 toString 方法,這個方法將從 byte 到 char 的轉化使用的預設編碼也是 ISO-8859-1,而我們也不能設定 Header 的其它解碼格式,所以如果你設定 Header 中有非 ASCII 字元解碼肯定會有亂碼。

我們在新增 Header 時也是同樣的道理,不要在 Header 中傳遞非 ASCII 字元,如果一定要傳遞的話,我們可以先將這些字元用 org.apache.catalina.util.URLEncoder 編碼然後再新增到 Header 中,這樣在瀏覽器到伺服器的傳遞過程中就不會丟失資訊了,如果我們要訪問這些項時再按照相應的字符集解碼就好了。

在前面提到了 POST 表單提交的引數的解碼是在第一次呼叫 request.getParameter 發生的,POST 表單引數傳遞方式與 QueryString 不同,它是通過 HTTP 的 BODY 傳遞到服務端的。當我們在頁面上點選 submit 按鈕時瀏覽器首先將根據 ContentType 的 Charset 編碼格式對錶單填的引數進行編碼然後提交到伺服器端,在伺服器端同樣也是用 ContentType 中字符集進行解碼。所以通過 POST 表單提交的引數一般不會出現問題,而且這個字符集編碼是我們自己設定的,可以通過 request.setCharacterEncoding(charset) 來設定。

另外針對 multipart/form-data 型別的引數,也就是上傳的檔案編碼同樣也是使用 ContentType 定義的字符集編碼,值得注意的地方是上傳檔案是用位元組流的方式傳輸到伺服器的本地臨時目錄,這個過程並沒有涉及到字元編碼,而真正編碼是在將檔案內容新增到 parameters 中,如果用這個編碼不能編碼時將會用預設編碼 ISO-8859-1 來編碼。

當用戶請求的資源已經成功獲取後,這些內容將通過 Response 返回給客戶端瀏覽器,這個過程先要經過編碼再到瀏覽器進行解碼。這個過程的編解碼字符集可以通過 response.setCharacterEncoding 來設定,它將會覆蓋 request.getCharacterEncoding 的值,並且通過 Header 的 Content-Type 返回客戶端,瀏覽器接受到返回的 socket 流時將通過 Content-Type 的 charset 來解碼,如果返回的 HTTP Header 中 Content-Type 沒有設定 charset,那麼瀏覽器將根據 Html 的 <meta HTTP-equiv="Content-Type" content="text/html; charset=GBK" /> 中的 charset 來解碼。如果也沒有定義的話,那麼瀏覽器將使用預設的編碼來解碼。

除了 URL 和引數編碼問題外,在服務端還有很多地方可能存在編碼,如可能需要讀取 xml、velocity 模版引擎、JSP 或者從資料庫讀取資料等。

xml 檔案可以通過設定頭來制定編碼格式

 <?xml version="1.0" encoding="UTF-8"?> 

Velocity 模版設定編碼格式:

 services.VelocityService.input.encoding=UTF-8 

JSP 設定編碼格式:

 <%@page contentType="text/html; charset=UTF-8"%> 

訪問資料庫都是通過客戶端 JDBC 驅動來完成,用 JDBC 來存取資料要和資料的內建編碼保持一致,可以通過設定 JDBC URL 來制定如 MySQL:url="jdbc:mysql://localhost:3306/DB?useUnicode=true&characterEncoding=GBK"。 

在瞭解了 Java Web 中可能需要編碼的地方後,下面看一下,當我們碰到一些亂碼時,應該怎麼處理這些問題?出現亂碼問題唯一的原因都是在 char 到 byte 或 byte 到 char 轉換中編碼和解碼的字符集不一致導致的,由於往往一次操作涉及到多次編解碼,所以出現亂碼時很難查詢到底是哪個環節出現了問題,下面就幾種常見的現象進行分析。

例如,字串“淘!我喜歡!”變成了“Ì Ô £ ¡Î Ò Ï²»¶ £ ¡”編碼過程如下圖所示


Figure xxx. Requires a heading

字串在解碼時所用的字符集與編碼字符集不一致導致漢字變成了看不懂的亂碼,而且是一個漢字字元變成兩個亂碼字元。

例如,字串“淘!我喜歡!”變成了“??????”編碼過程如下圖所示


Figure xxx. Requires a heading

將中文和中文符號經過不支援中文的 ISO-8859-1 編碼後,所有字元變成了“?”,這是因為用 ISO-8859-1 進行編解碼時遇到不在碼值範圍內的字元時統一用 3f 表示,這也就是通常所說的“黑洞”,所有 ISO-8859-1 不認識的字元都變成了“?”。

例如,字串“淘!我喜歡!”變成了“????????????”編碼過程如下圖所示


Figure xxx. Requires a heading

這種情況比較複雜,中文經過多次編碼,但是其中有一次編碼或者解碼不對仍然會出現中文字元變成“?”現象,出現這種情況要仔細檢視中間的編碼環節,找出出現編碼錯誤的地方。

還有一種情況是在我們通過 request.getParameter 獲取引數值時,當我們直接呼叫

 String value = request.getParameter(name); 

會出現亂碼,但是如果用下面的方式

 String value = String(request.getParameter(name).getBytes("
 ISO-8859-1"), "GBK"); 
			

解析時取得的 value 會是正確的漢字字元,這種情況是怎麼造成的呢?

看下如所示:


Figure xxx. Requires a heading

這種情況是這樣的,ISO-8859-1 字符集的編碼範圍是 0000-00FF,正好和一個位元組的編碼範圍相對應。這種特性保證了使用 ISO-8859-1 進行編碼和解碼可以保持編碼數值“不變”。雖然中文字元在經過網路傳輸時,被錯誤地“拆”成了兩個歐洲字元,但由於輸出時也是用 ISO-8859-1,結果被“拆”開的中文字的兩半又被合併在一起,從而又剛好組成了一個正確的漢字。雖然最終能取得正確的漢字,但是還是不建議用這種不正常的方式取得引數值,因為這中間增加了一次額外的編碼與解碼,這種情況出現亂碼時因為 Tomcat 的配置檔案中 useBodyEncodingForURI 配置項沒有設定為”true”,從而造成第一次解析式用 ISO-8859-1 來解析才造成亂碼的。

本文首先總結了幾種常見編碼格式的區別,然後介紹了支援中文的幾種編碼格式,並比較了它們的使用場景。接著介紹了 Java 那些地方會涉及到編碼問題,已經 Java 中如何對編碼的支援。並以網路 I/O 為例重點介紹了 HTTP 請求中的存在編碼的地方,以及 Tomcat 對 HTTP 協議的解析,最後分析了我們平常遇到的亂碼問題出現的原因。

綜上所述,要解決中文問題,首先要搞清楚哪些地方會引起字元到位元組的編碼以及位元組到字元的解碼,最常見的地方就是讀取會儲存資料到磁碟,或者資料要經過網路傳輸。然後針對這些地方搞清楚操作這些資料的框架的或系統是如何控制編碼的,正確設定編碼格式,避免使用軟體預設的或者是作業系統平臺預設的編碼格式。

JDK自帶的轉碼工具 native2ascii.exe

activetoascii國際化中文轉換it 分類:java
在做Java開發的時候,常常會出現一些亂碼,或者無法正確識別或讀取的檔案,原因是編碼方式的不一致。native2ascii是sun java sdk提供的一個工具。用來將別的文字類檔案(比如*.txt,*.ini,*.properties,*.java等等)編碼轉為Unicode編碼。
一.獲取native2ascii
  安裝了jdk後,假如你是在windows上安裝,那麼在jdk的安裝目錄下,會有一個bin目錄,其中native2ascii.exe正是。

二.使用方法
命令列格式:

native2ascii.exe -[options] [inputfile [outputfile]]
其中:
  -[options]表示命令開關,有兩個選項可供選擇:
    -reverse:用Latin-1或Unicode編碼把檔案轉換成本地編碼格式
    -encoding encoding_name:要把檔案轉換的目標編碼
  inputfile:表示輸入檔案全名。
  outputfile:輸出檔名。如果缺少此引數,將輸出到控制檯。

具體介紹如下:
1.命令列互動方式
  在這種方式下通過手工輸入字元實時在Console中實現互動轉碼。退出按Ctrl+c
  1)本地編碼→Latin編碼:
   直接敲入native2ascii.exe,程式游標會停在下一行,這時你可以直接敲入想要轉換的字元,例如:

D:\jdk1.5.0_06\bin>native2ascii.exe
  我們
  \u6211\u4eec
  2)Latin-1編碼→指定編碼:
   直接敲入native2ascii.exe -reverse,程式游標會停在下一行,這時你可以直接敲入想要轉換的字元,例如:

D:\jdk1.5.0_06\bin>native2ascii.exe -reverse
  \u6211\u4eec
  我們
2.檔案轉換方式
  在這種方式下通過指定inputfile和outputfile來實現轉碼。
  1)指定編碼→Latin編碼:
    例如:

D:\jdk1.5.0_06\bin>native2ascii.exe -encoding utf8 abc.txt bcd.txt
  2)Latin-1編碼→指定編碼:
    例如:

D:\jdk1.5.0_06\bin>native2ascii.exe -reverse utf8 abc.txt bcd.txt

native2ascii.exe 是 Java 的一個檔案轉碼工具,是將特殊各異的內容轉為 用指定的編碼標準文體形式統一的表現出來,它通常位於 JDK_home\bin 目錄下,安裝好 Java SE 後,可在命令列直接使用 native2ascii 命令進行轉碼,示例:

native2ascii -encoding 8859_1 c:\test.txt c:\temp.txt
將 test.txt 檔案內容用 8859_1 轉碼,另存為 temp.txt 檔案
格式:native2ascii -[options] [inputfile [outputfile]]

引數選項 options
-reverse:將 Latin-1 或 Unicode 編碼轉為本地編碼
-encoding encoding_name:指定轉換時使用的編碼
inputfile:要轉換的檔案
outputfile:轉換後的檔案

互轉(-encoding,非英文內容(如中文)轉為編碼符 或 編碼符之間的轉換),
逆轉(-reverse,通常是將編碼符轉為非英文內容,或非英文內容之間的轉換),
逆轉時被轉的檔案編碼和本地編碼需一致,示例:

中文轉為 ISO 8859_1 編碼後,將 8859_1 碼轉為中文:
native2ascii -encoding 8859_1 c:\a.txt c:\b.txt,將 a 用 8859_1 轉碼,存為 b (8859_1 碼)
native2ascii -encoding GBK c:\b.txt c:\c.txt,將 b 用 GBK 轉碼,存為 c (GBK 碼)
native2ascii -reverse c:\c.txt c:\d.txt,將 GBK 編碼 c 用本地編碼轉碼,存為 d (中文內容)

中文轉為 GBK 編碼後,將 GBK 碼轉為中文:
native2ascii -encoding GBK c:\a.txt c:\b.txt,將 a 用 GBK 轉碼,存為 b (GBK 碼)
native2ascii -reverse c:\b.txt c:\c.txt,將 GBK 編碼 b 用本地編碼轉碼,存為 c (中文內容)

例如struts和struts2中的國際化utf-8的轉換方式:
native2ascii -encoding UTF-8 ApplicationResources_zh_src.properties ApplicationResources_zh.properties

Java判斷檔案編碼格式


1:簡單判斷是UTF-8或不是UTF-8,因為一般除了UTF-8之外就是GBK,所以就設定預設為GBK。


    按照給定的字符集儲存檔案時,在檔案的最開頭的三個位元組中就有可能儲存著編碼資訊,所以,基本的原理就是隻要讀出檔案前三個位元組,判定這些位元組的值,就可以得知其編碼的格式。其實,如果專案執行的平臺就是中文作業系統,如果這些文字檔案在專案內產生,即開發人員可以控制文字的編碼格式,只要判定兩種常見的編碼就可以了:GBK和UTF-8。由於中文Windows預設的編碼是GBK,所以一般只要判定UTF-8編碼格式。
    對於UTF-8編碼格式的文字檔案,其前3個位元組的值就是-17、-69、-65,所以,判定是否是UTF-8編碼格式的程式碼片段如下:
File file = new File(path);
InputStream in= new java.io.FileInputStream(file);
byte[] b = new byte[3];
in.read(b);
in.close();
if (b[0] == -17 && b[1] == -69 && b[2] == -65)
System.out.println(file.getName() + ":編碼為UTF-8");
else
System.out.println(file.getName() + ":可能是GBK,也可能是其他編碼");

通過這個方法,只要知道檔案頭儲存的編碼資訊,可以類似判斷檔案是否是某一編碼格式的檔案,不再限於是否是utf-8,就不在贅述。注意這個方法的侷限:檔案頭必須有檔案儲存編碼的資訊,對於不規範的檔案或被篡改過的就另當別論,呵呵。


2:更復雜的檔案編碼檢測,可以使用一個開源專案cpdetector,它所在的網址是:http://cpdetector.sourceforge.net/。它的類庫很小,只有500K左右,cpDetector是基於統計學原理的,不保證完全正確,利用該類庫判定文字檔案的程式碼如下:

讀外部檔案(先利用cpdetector檢測檔案的編碼格式,然後用檢測到的編碼方式去讀檔案)

/**
  * 利用第三方開源包cpdetector獲取檔案編碼格式
  *
  * @param path
  *            要判斷檔案編碼格式的原始檔的路徑
  */
 public static String getFileEncode(String path) {
  /*
   * detector是探測器,它把探測任務交給具體的探測實現類的例項完成。
   * cpDetector內建了一些常用的探測實現類,這些探測實現類的例項可以通過add方法 加進來,如ParsingDetector、
   * JChardetFacade、ASCIIDetector、UnicodeDetector。
   * detector按照“誰最先返回非空的探測結果,就以該結果為準”的原則返回探測到的
   * 字符集編碼。使用需要用到三個第三方JAR包:antlr.jar、chardet.jar和cpdetector.jar
   * cpDetector是基於統計學原理的,不保證完全正確。
   */
  CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance();
  /*
   * ParsingDetector可用於檢查HTML、XML等檔案或字元流的編碼,構造方法中的引數用於
   * 指示是否顯示探測過程的詳細資訊,為false不顯示。
   */
  detector.add(new ParsingDetector(false));
  /*
   * JChardetFacade封裝了由Mozilla組織提供的JChardet,它可以完成大多數檔案的編碼
   * 測定。所以,一般有了這個探測器就可滿足大多數專案的要求,如果你還不放心,可以
   * 再多加幾個探測器,比如下面的ASCIIDetector、UnicodeDetector等。
   */
  detector.add(JChardetFacade.getInstance());// 用到antlr.jar、chardet.jar
  // ASCIIDetector用於ASCII編碼測定
  detector.add(ASCIIDetector.getInstance());
  // UnicodeDetector用於Unicode家族編碼的測定
  detector.add(UnicodeDetector.getInstance());
  java.nio.charset.Charset charset = null;
  File f = new File(path);
  try {
   charset = detector.detectCodepage(f.toURI().toURL());
  } catch (Exception ex) {
   ex.printStackTrace();
  }
  if (charset != null)
   return charset.name();
  else
   return null;
 }
 String charsetName = getFileEncode(configFilePath);
 System.out.println(charsetName);
 inputStream = new FileInputStream(configFile);
 BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, charsetName));

讀jar包內部資原始檔(先利用cpdetector檢測jar內部的資原始檔的編碼格式,然後以檢測到的編碼方式去讀檔案):
/**
  * 利用第三方開源包cpdetector獲取URL對應的檔案編碼
  *
  * @param path
  *            要判斷檔案編碼格式的原始檔的URL
  */
 public static String getFileEncode(URL url) {
  /*
   * detector是探測器,它把探測任務交給具體的探測實現類的例項完成。
   * cpDetector內建了一些常用的探測實現類,這些探測實現類的例項可以通過add方法 加進來,如ParsingDetector、
   * JChardetFacade、ASCIIDetector、UnicodeDetector。
   * detector按照“誰最先返回非空的探測結果,就以該結果為準”的原則返回探測到的
   * 字符集編碼。使用需要用到三個第三方JAR包:antlr.jar、chardet.jar和cpdetector.jar
   * cpDetector是基於統計學原理的,不保證完全正確。
   */
  CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance();
  /*
   * ParsingDetector可用於檢查HTML、XML等檔案或字元流的編碼,構造方法中的引數用於
   * 指示是否顯示探測過程的詳細資訊,為false不顯示。
   */
  detector.add(new ParsingDetector(false));
  /*
   * JChardetFacade封裝了由Mozilla組織提供的JChardet,它可以完成大多數檔案的編碼
   * 測定。所以,一般有了這個探測器就可滿足大多數專案的要求,如果你還不放心,可以
   * 再多加幾個探測器,比如下面的ASCIIDetector、UnicodeDetector等。
   */
  detector.add(JChardetFacade.getInstance());// 用到antlr.jar、chardet.jar
  // ASCIIDetector用於ASCII編碼測定
  detector.add(ASCIIDetector.getInstance());
  // UnicodeDetector用於Unicode家族編碼的測定
  detector.add(UnicodeDetector.getInstance());
  java.nio.charset.Charset charset = null;
  try {
   charset = detector.detectCodepage(url);
  } catch (Exception ex) {
   ex.printStackTrace();
  }
  if (charset != null)
   return charset.name();
  else
   return null;
 }
 URL url = CreateStationTreeModel.class.getResource("/resource/" + "配置檔案");
 URLConnection urlConnection = url.openConnection();
 inputStream=urlConnection.getInputStream();
 String charsetName = getFileEncode(url);
 System.out.println(charsetName);
 BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, charsetName));

3:探測任意輸入的文字流的編碼,方法是呼叫其過載形式:


charset=detector.detectCodepage(待測的文字輸入流,測量該流所需的讀入位元組數);

上面的位元組數由程式設計師指定,位元組數越多,判定越準確,當然時間也花得越長。要注意,位元組數的指定不能超過文字流的最大長度。


4:判定檔案編碼的具體應用舉例:
    屬性檔案(.properties)是Java程