java 是如何編碼解碼的

2018-09-28 19:24 更新

java是如何編碼解碼的

在上篇博客中LZ闡述了java各個(gè)渠道轉(zhuǎn)碼的過(guò)程,闡述了java在運(yùn)行過(guò)程中那些步驟在進(jìn)行轉(zhuǎn)碼,在這些轉(zhuǎn)碼過(guò)程中如果一處出現(xiàn)問(wèn)題就很有可能會(huì)產(chǎn)生亂碼!下面LZ就講述java在轉(zhuǎn)碼過(guò)程中是如何來(lái)進(jìn)行編碼和解碼操作的。

編碼&解碼

在上篇博客中LZ闡述了三個(gè)渠道的編碼轉(zhuǎn)換過(guò)程,下面LZ將結(jié)束java在那些場(chǎng)合需要進(jìn)行編碼和解碼操作,并詳序中間的過(guò)程,進(jìn)一步掌握java的編碼和解碼過(guò)程。在java中主要有四個(gè)場(chǎng)景需要進(jìn)行編碼解碼操作:

1:I/O操作

2:內(nèi)存

3:數(shù)據(jù)庫(kù)

4:javaWeb

下面主要介紹前面兩種場(chǎng)景,數(shù)據(jù)庫(kù)部分只要設(shè)置正確編碼格式就不會(huì)有什么問(wèn)題,javaWeb場(chǎng)景過(guò)多需要了解URL、get、POST的編碼,servlet的解碼,所以javaWeb場(chǎng)景下節(jié)LZ介紹。

I/O操作

在前面LZ就提過(guò)亂碼問(wèn)題無(wú)非就是轉(zhuǎn)碼過(guò)程中編碼格式的不統(tǒng)一產(chǎn)生的,比如編碼時(shí)采用UTF-8,解碼采用GBK,但最根本的原因是字符到字節(jié)或者字節(jié)到字符的轉(zhuǎn)換出問(wèn)題了,而這中情況的轉(zhuǎn)換最主要的場(chǎng)景就是I/O操作的時(shí)候。當(dāng)然I/O操作主要包括網(wǎng)絡(luò)I/O(也就是javaWeb)和磁盤(pán)I/O。網(wǎng)絡(luò)I/O下節(jié)介紹。

首先我們先看I/O的編碼操作。

InputStream為字節(jié)輸入流的所有類(lèi)的超類(lèi),Reader為讀取字符流的抽象類(lèi)。java讀取文件的方式分為按字節(jié)流讀取和按字符流讀取,其中InputStream、Reader是這兩種讀取方式的超類(lèi)。

按字節(jié)

我們一般都是使用InputStream.read()方法在數(shù)據(jù)流中讀取字節(jié)(read()每次都只讀取一個(gè)字節(jié),效率非常慢,我們一般都是使用read(byte[])),然后保存在一個(gè)byte[]數(shù)組中,最后轉(zhuǎn)換為String。在我們讀取文件時(shí),讀取字節(jié)的編碼取決于文件所使用的編碼格式,而在轉(zhuǎn)換為String過(guò)程中也會(huì)涉及到編碼的問(wèn)題,如果兩者之間的編碼格式不同可能會(huì)出現(xiàn)問(wèn)題。例如存在一個(gè)問(wèn)題test.txt編碼格式為UTF-8,那么通過(guò)字節(jié)流讀取文件時(shí)所獲得的數(shù)據(jù)流編碼格式就是UTF-8,而我們?cè)谵D(zhuǎn)化成String過(guò)程中如果不指定編碼格式,則默認(rèn)使用系統(tǒng)編碼格式(GBK)來(lái)解碼操作,由于兩者編碼格式不一致,那么在構(gòu)造String過(guò)程肯定會(huì)產(chǎn)生亂碼,如下:

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);

輸出結(jié)果:锘挎垜鏄?cm

test.txt中的內(nèi)容為:我是 cm。

要想不出現(xiàn)亂碼,在構(gòu)造String過(guò)程中指定編碼格式,使得編碼解碼時(shí)兩者編碼格式保持一致即可:

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

按字符

其實(shí)字符流可以看做是一種包裝流,它的底層還是采用字節(jié)流來(lái)讀取字節(jié),然后它使用指定的編碼方式將讀取字節(jié)解碼為字符。在java中Reader是讀取字符流的超類(lèi)。所以從底層上來(lái)看按字節(jié)讀取文件和按字符讀取沒(méi)什么區(qū)別。在讀取的時(shí)候字符讀取每次是讀取留個(gè)字節(jié),字節(jié)流每次讀取一個(gè)字節(jié)。

字節(jié)&字符轉(zhuǎn)換

字節(jié)轉(zhuǎn)換為字符一定少不了InputStreamReader。API解釋如下:InputStreamReader 是字節(jié)流通向字符流的橋梁:它使用指定的 charset 讀取字節(jié)并將其解碼為字符。它使用的字符集可以由名稱(chēng)指定或顯式給定,或者可以接受平臺(tái)默認(rèn)的字符集。 每次調(diào)用 InputStreamReader 中的一個(gè) read() 方法都會(huì)導(dǎo)致從底層輸入流讀取一個(gè)或多個(gè)字節(jié)。要啟用從字節(jié)到字符的有效轉(zhuǎn)換,可以提前從底層流讀取更多的字節(jié),使其超過(guò)滿(mǎn)足當(dāng)前讀取操作所需的字節(jié)。API解釋非常清楚,InputStreamReader在底層讀取文件時(shí)仍然采用字節(jié)讀取,讀取字節(jié)后它需要根據(jù)一個(gè)指定的編碼格式來(lái)解析為字符,如果沒(méi)有指定編碼格式則采用系統(tǒng)默認(rèn)編碼格式。

String file = "C:\\test.txt"; 
         String charset = "UTF-8"; 
         // 寫(xiě)字符換轉(zhuǎn)成字節(jié)流
         FileOutputStream outputStream = new FileOutputStream(file); 
         OutputStreamWriter writer = new OutputStreamWriter(outputStream, charset); 
         try { 
            writer.write("我是 cm"); 
         } finally { 
            writer.close(); 
         } 

         // 讀取字節(jié)轉(zhuǎn)換成字符
         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);

內(nèi)存

首先我們看下面這段簡(jiǎn)單的代碼

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

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

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

內(nèi)部調(diào)用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)方法首先調(diào)用系統(tǒng)的默認(rèn)編碼格式,如果沒(méi)有指定編碼格式則默認(rèn)使用ISO-8859-1編碼格式進(jìn)行編碼操作,進(jìn)一步深入如下:

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

同樣的方法可以看到new String 的構(gòu)造函數(shù)內(nèi)部是調(diào)用

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對(duì)編碼格式的處理是一樣的。

對(duì)于以上兩種情況我們只需要設(shè)置統(tǒng)一的編碼格式一般都不會(huì)產(chǎn)生亂碼問(wèn)題。

編碼&編碼格式

首先先看看java編碼類(lèi)圖[1]

首先根據(jù)指定的chart設(shè)置ChartSet類(lèi),然后根據(jù)ChartSet創(chuàng)建ChartSetEncoder對(duì)象,最后再調(diào)用 CharsetEncoder.encode 對(duì)字符串進(jìn)行編碼,不同的編碼類(lèi)型都會(huì)對(duì)應(yīng)到一個(gè)類(lèi)中,實(shí)際的編碼過(guò)程是在這些類(lèi)中完成的。下面時(shí)序圖展示詳細(xì)的編碼過(guò)程:

通過(guò)這編碼的類(lèi)圖和時(shí)序圖可以了解編碼的詳細(xì)過(guò)程。下面將通過(guò)一段簡(jiǎn)單的代碼對(duì)ISO-8859-1、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轉(zhuǎn)換為16進(jìn)制
     */
    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轉(zhuǎn)換為16進(jìn)制
     */
    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

通過(guò)程序我們可以看到“我是 cm”的結(jié)果為:

char[]:6211 662f 20 63 6d

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

圖如下:

更多&參考文獻(xiàn)

對(duì)于這兩種場(chǎng)景我們只需要設(shè)置一致正確的編碼一般都不會(huì)產(chǎn)生亂碼問(wèn)題,通過(guò)LZ上面的闡述對(duì)于java編碼解碼的過(guò)程應(yīng)該會(huì)有一個(gè)比較清楚的認(rèn)識(shí)。其實(shí)在java中產(chǎn)生亂碼的主要場(chǎng)景是在javaWeb中,所以LZ下篇博文就來(lái)講解javaWeb中的亂碼產(chǎn)生情形。

1、Java 編程技術(shù)中漢字問(wèn)題的分析及解決:http://www.ibm.com/developerworks/cn/java/java_chinese/。

以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)