java中文亂碼解決之道(五)-----java是如何編碼解碼的

在上篇博客中LZ闡述了java各個渠道轉碼的過程,闡述了java在運行過程當中那些步驟在進行轉碼,在這些轉碼過程當中若是一處出現問題就頗有可能會產生亂碼!下面LZ就講述java在轉碼過程當中是如何來進行編碼和解碼操做的。java

編碼&解碼

在上篇博客中LZ闡述了三個渠道的編碼轉換過程,下面LZ將結束java在那些場合須要進行編碼和解碼操做,並詳序中間的過程,進一步掌握java的編碼和解碼過程。在java中主要有四個場景須要進行編碼解碼操做:數據庫

1:I/O操做編程

2:內存數組

3:數據庫網絡

4:javaWebapp

下面主要介紹前面兩種場景,數據庫部分只要設置正確編碼格式就不會有什麼問題,javaWeb場景過多須要瞭解URL、get、POST的編碼,servlet的解碼,因此javaWeb場景下節LZ介紹。ide

I/O操做

在前面LZ就提過亂碼問題無非就是轉碼過程當中編碼格式的不統一產生的,好比編碼時採用UTF-8,解碼採用GBK,但最根本的緣由是字符到字節或者字節到字符的轉換出問題了,而這中狀況的轉換最主要的場景就是I/O操做的時候。固然I/O操做主要包括網絡I/O(也就是javaWeb)和磁盤I/O。網絡I/O下節介紹。函數

首先咱們先看I/O的編碼操做。ui

201412300001

InputStream爲字節輸入流的全部類的超類,Reader爲讀取字符流的抽象類。java讀取文件的方式分爲按字節流讀取和按字符流讀取,其中InputStream、Reader是這兩種讀取方式的超類。this

按字節

咱們通常都是使用InputStream.read()方法在數據流中讀取字節(read()每次都只讀取一個字節,效率很是慢,咱們通常都是使用read(byte[])),而後保存在一個byte[]數組中,最後轉換爲String。在咱們讀取文件時,讀取字節的編碼取決於文件所使用的編碼格式,而在轉換爲String過程當中也會涉及到編碼的問題,若是二者之間的編碼格式不一樣可能會出現問題。例如存在一個問題test.txt編碼格式爲UTF-8,那麼經過字節流讀取文件時所得到的數據流編碼格式就是UTF-8,而咱們在轉化成String過程當中若是不指定編碼格式,則默認使用系統編碼格式(GBK)來解碼操做,因爲二者編碼格式不一致,那麼在構造String過程確定會產生亂碼,以下:

File file = new File("C:\\test.txt");
        InputStream input = new FileInputStream(file);
        StringBuffer buffer = new StringBuffer();
        byte[] bytes = new byte[1024];
        for(int n ; (n = input.read(bytes))!=-1 ; ){
            buffer.append(new String(bytes,0,n));
        }
        System.out.println(buffer);

輸出結果:鍩挎垜鏄?cm

test.txt中的內容爲:我是 cm。

要想不出現亂碼,在構造String過程當中指定編碼格式,使得編碼解碼時二者編碼格式保持一致便可:

buffer.append(new String(bytes,0,n,"UTF-8"));

按字符

其實字符流能夠看作是一種包裝流,它的底層仍是採用字節流來讀取字節,而後它使用指定的編碼方式將讀取字節解碼爲字符。在java中Reader是讀取字符流的超類。因此從底層上來看按字節讀取文件和按字符讀取沒什麼區別。在讀取的時候字符讀取每次是讀取留個字節,字節流每次讀取一個字節。

字節&字符轉換

字節轉換爲字符必定少不了InputStreamReader。API解釋以下:InputStreamReader 是字節流通向字符流的橋樑:它使用指定的 charset 讀取字節並將其解碼爲字符。它使用的字符集能夠由名稱指定或顯式給定,或者能夠接受平臺默認的字符集。 每次調用 InputStreamReader 中的一個 read() 方法都會致使從底層輸入流讀取一個或多個字節。要啓用從字節到字符的有效轉換,能夠提早從底層流讀取更多的字節,使其超過知足當前讀取操做所需的字節。API解釋很是清楚,InputStreamReader在底層讀取文件時仍然採用字節讀取,讀取字節後它須要根據一個指定的編碼格式來解析爲字符,若是沒有指定編碼格式則採用系統默認編碼格式。

String file = "C:\\test.txt"; 
         String charset = "UTF-8"; 
         // 寫字符換轉成字節流
         FileOutputStream outputStream = new FileOutputStream(file); 
         OutputStreamWriter writer = new OutputStreamWriter(outputStream, charset); 
         try { 
            writer.write("我是 cm"); 
         } 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(buf, 0, count); 
            } 
         } finally { 
            reader.close(); 
         }
         System.out.println(buffer);

內存

首先咱們看下面這段簡單的代碼

String s = "我是 cm"; 
         byte[] bytes = s.getBytes(); 
         String s1 = new String(bytes,"GBK"); 
         String s2 = new String(bytes);

在這段代碼中咱們看到了三處編碼轉換過程(一次編碼,兩次解碼)。先看String.getTytes():

public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }

內部調用StringCoding.encode()方法操做:

static byte[] encode(char[] ca, int off, int len) {
        String csn = Charset.defaultCharset().name();
        try {
            // use charset name encode() variant which provides caching.
            return encode(csn, ca, off, len);
        } catch (UnsupportedEncodingException x) {
            warnUnsupportedCharset(csn);
        }
        try {
            return encode("ISO-8859-1", ca, off, len);
        } catch (UnsupportedEncodingException x) {
            // If this code is hit during VM initialization, MessageUtils is
            // the only way we will be able to get any kind of error message.
            MessageUtils.err("ISO-8859-1 charset not available: "
                             + x.toString());
            // If we can not find ISO-8859-1 (a required encoding) then things
            // are seriously wrong with the installation.
            System.exit(1);
            return null;
        }
    }

encode(char[] paramArrayOfChar, int paramInt1, int paramInt2)方法首先調用系統的默認編碼格式,若是沒有指定編碼格式則默認使用ISO-8859-1編碼格式進行編碼操做,進一步深刻以下:

String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;

一樣的方法能夠看到new String 的構造函數內部是調用StringCoding.decode()方法:

public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        this.value =  StringCoding.decode(charset, bytes, offset, length);
    }

decode方法和encode對編碼格式的處理是同樣的。

對於以上兩種狀況咱們只須要設置統一的編碼格式通常都不會產生亂碼問題。

編碼&編碼格式

首先先看看java編碼類圖[1]

201412300003

首先根據指定的chart設置ChartSet類,而後根據ChartSet建立ChartSetEncoder對象,最後再調用 CharsetEncoder.encode 對字符串進行編碼,不一樣的編碼類型都會對應到一個類中,實際的編碼過程是在這些類中完成的。下面時序圖展現詳細的編碼過程:

201412300002

經過這編碼的類圖和時序圖能夠了解編碼的詳細過程。下面將經過一段簡單的代碼對ISO-8859-一、GBK、UTF-8編碼

public class Test02 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String string = "我是 cm";
        Test02.printChart(string.toCharArray());
        Test02.printChart(string.getBytes("ISO-8859-1"));
        Test02.printChart(string.getBytes("GBK"));
        Test02.printChart(string.getBytes("UTF-8"));
    }
    
    /**
     * char轉換爲16進制
     */
    public static void printChart(char[] chars){
        for(int i = 0 ; i < chars.length ; i++){
            System.out.print(Integer.toHexString(chars[i]) + " "); 
        }
        System.out.println("");
    }
    
    /**
     * byte轉換爲16進制
     */
    public static void printChart(byte[] bytes){
        for(int i = 0 ; i < bytes.length ; i++){
            String hex = Integer.toHexString(bytes[i] & 0xFF); 
             if (hex.length() == 1) { 
               hex = '0' + hex; 
             } 
             System.out.print(hex.toUpperCase() + " "); 
        }
        System.out.println("");
    }
}
-------------------------outPut:
6211 662f 20 63 6d 
3F 3F 20 63 6D 
CE D2 CA C7 20 63 6D 
E6 88 91 E6 98 AF 20 63 6D

經過程序咱們能夠看到「我是 cm」的結果爲:

char[]:6211 662f 20 63 6d

ISO-8859-1:3F 3F 20 63 6D
GBK:CE D2 CA C7 20 63 6D
UTF-8:E6 88 91 E6 98 AF 20 63 6D

圖以下:

201412310001

更多&參考文獻

對於這兩種場景咱們只須要設置一致正確的編碼通常都不會產生亂碼問題,經過LZ上面的闡述對於java編碼解碼的過程應該會有一個比較清楚的認識。其實在java中產生亂碼的主要場景是在javaWeb中,因此LZ下篇博文就來說解javaWeb中的亂碼產生情形。

一、Java 編程技術中漢字問題的分析及解決:http://www.ibm.com/developerworks/cn/java/java_chinese/


-----原文出自:http://cmsblogs.com/?p=1491,請尊重做者辛勤勞動成果,轉載說明出處.

-----我的站點:http://cmsblogs.com

相關文章
相關標籤/搜索