1. 程式人生 > >JAVA 編碼中文問題系統透徹講解 UNICODE GBK UTF-8 ISO-8859-1 之間的區別

JAVA 編碼中文問題系統透徹講解 UNICODE GBK UTF-8 ISO-8859-1 之間的區別

宣告

目錄

步驟 1 : 編碼概念 步驟 2 : 常見編碼 步驟 3 : UNICODE和UTF 步驟 4 : Java採用的是Unicode 步驟 5 : 一個漢字使用不同編碼方式的表現 步驟 6 : 檔案的編碼方式-記事本 步驟 7 : 檔案的編碼方式-eclipse 步驟 8 : 用FileInputStream 位元組流正確讀取中文 步驟 9 : 用FileReader 字元流正確讀取中文 步驟 10 : 練習-數字對應的中文 步驟 11 : 答案-數字對應的中文 步驟 12 : 練習-移除BOM 步驟 13 : 答案-移除BOM

步驟 1 : 編碼概念

計算機存放資料只能存放數字,所有的字元都會被轉換為不同的數字。 就像一個棋盤一樣,不同的字,處於不同的位置,而不同的位置,有不同的數字編號。 有的棋盤很小,只能放數字和英文 有的大一點,還能放中文 有的“足夠”大,能夠放下世界人民所使用的所有文字和符號

如圖所示,英文字元 A 能夠放在所有的棋盤裡,而且位置都差不多 中文字元, 中文字元 中 能夠放在後兩種棋盤裡,並且位置不一樣,而且在小的那個棋盤裡,就放不下中文

步驟 2 : 常見編碼

工作後經常接觸的編碼方式有如下幾種: ISO-8859-1 ASCII 數字和西歐字母 GBK GB2312 BIG5 中文 UNICODE (統一碼,萬國碼)

其中 ISO-8859-1 包含 ASCII GB2312 是簡體中文,BIG5是繁體中文,GBK同時包含簡體和繁體以及日文。 UNICODE 包括了所有的文字,無論中文,英文,藏文,法文,世界所有的文字都包含其中

步驟 3 : UNICODE和UTF

根據前面的學習,我們瞭解到不同的編碼方式對應不同的棋盤,而UNICODE因為要存放所有的資料,那麼它的棋盤是最大的。 不僅如此,棋盤裡每個數字都是很長的(4個位元組),因為不僅要表示字母,還要表示漢字等。

如果完全按照UNICODE的方式來儲存資料,就會有很大的浪費。 比如在ISO-8859-1中,a 字元對應的數字是0x61 而UNICODE中對應的數字是 0x00000061,倘若一篇文章大部分都是英文字母,那麼按照UNICODE的方式進行資料儲存就會消耗很多空間

在這種情況下,就出現了UNICODE的各種減肥子編碼, 比如UTF-8對數字和字母就使用一個位元組,而對漢字就使用3個位元組,從而達到了減肥還能保證健康的效果

UTF-8,UTF-16和UTF-32 針對不同型別的資料有不同的減肥效果,一般說來UTF-8是比較常用的方式

UTF-8,UTF-16和UTF-32 彼此的區別在此不作贅述,有興趣的可以參考 unicode-百度百科

步驟 4 : Java採用的是Unicode

寫在.java原始碼中的漢字,在執行之後,都會變成JVM中的字元。 而這些中文字符采用的編碼方式,都是使用UNICODE. "中"字對應的UNICODE是4E2D,所以在記憶體中,實際儲存的資料就是十六進位制的0x4E2D, 也就是十進位制的20013。

package stream;
 
public class TestStream {
    public static void main(String[] args) {
        String str = "中";
    }
}

步驟 5 : 一個漢字使用不同編碼方式的表現

以字元 中 為例,檢視其在不同編碼方式下的值是多少

也即在不同的棋盤上的位置

package stream;

import java.io.UnsupportedEncodingException;

public class TestStream {

	public static void main(String[] args) {
		String str = "中";
		showCode(str);
	}

	private static void showCode(String str) {
		String[] encodes = { "BIG5", "GBK", "GB2312", "UTF-8", "UTF-16", "UTF-32" };
		for (String encode : encodes) {
			showCode(str, encode);
		}

	}

	private static void showCode(String str, String encode) {
		try {
			System.out.printf("字元: \"%s\" 的在編碼方式%s下的十六進位制值是%n", str, encode);
			byte[] bs = str.getBytes(encode);

			for (byte b : bs) {
				int i = b&0xff;
				System.out.print(Integer.toHexString(i) + "\t");
			}
			System.out.println();
			System.out.println();
		} catch (UnsupportedEncodingException e) {
			System.out.printf("UnsupportedEncodingException: %s編碼方式無法解析字元%s\n", encode, str);
		}
	}
}

步驟 6 : 檔案的編碼方式-記事本

接下來講,字元在檔案中的儲存 字元儲存在檔案中肯定也是以數字形式儲存的,即對應在不同的棋盤上的不同的數字 用記事本開啟任意文字檔案,並且另存為,就能夠在編碼這裡看到一個下拉。 ANSI 這個不是ASCII的意思,而是採用本地編碼的意思。如果你是中文的作業系統,就會使GBK,如果是英文的就會是ISO-8859-1 Unicode UNICODE原生的編碼方式 Unicode big endian 另一個 UNICODE編碼方式 UTF-8 最常見的UTF-8編碼方式,數字和字母用一個位元組, 漢字用3個位元組。 在這裡插入圖片描述

步驟 7 : 檔案的編碼方式-eclipse

eclipse也有類似的編碼方式,右鍵任意文字檔案,點選最下面的"property" 就可以看到Text file encoding 也有ISO-8859-1,GBK,UTF-8等等選項。 其他的US-ASCII,UTF-16,UTF-16BE,UTF-16LE不常用。

步驟 8 : 用FileInputStream 位元組流正確讀取中文

為了能夠正確的讀取中文內容 1. 必須瞭解文字是以哪種編碼方式儲存字元的 2. 使用位元組流讀取了文字後,再使用對應的編碼方式去識別這些數字,得到正確的字元 如本例,一個檔案中的內容是字元中,編碼方式是GBK,那麼讀出來的資料一定是D6D0。 再使用GBK編碼方式識別D6D0,就能正確的得到字元中

注: 在GBK的棋盤上找到的中字後,JVM會自動找到中在UNICODE這個棋盤上對應的數字,並且以UNICODE上的數字儲存在記憶體中。

步驟 9 : 用FileReader 字元流正確讀取中文

FileReader得到的是字元,所以一定是已經把位元組根據某種編碼識別成了字元了 而FileReader使用的編碼方式是Charset.defaultCharset()的返回值,如果是中文的作業系統,就是GBK FileReader是不能手動設定編碼方式的,為了使用其他的編碼方式,只能使用InputStreamReader來代替,像這樣:

new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"));

在本例中,用記事本另存為UTF-8格式,然後用UTF-8就能識別對應的中文了。

解釋: 為什麼中字前面有一個? 如果是使用記事本另存為UTF-8的格式,那麼在第一個位元組有一個標示符,叫做BOM用來標誌這個檔案是用UTF-8來編碼的。

package stream;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

public class TestStream {

	public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException {
		File f = new File("E:\\project\\j2se\\src\\test.txt");
		System.out.println("預設編碼方式:"+Charset.defaultCharset());
		//FileReader得到的是字元,所以一定是已經把位元組根據某種編碼識別成了字元了
		//而FileReader使用的編碼方式是Charset.defaultCharset()的返回值,如果是中文的作業系統,就是GBK
		try (FileReader fr = new FileReader(f)) {
			char[] cs = new char[(int) f.length()];
			fr.read(cs);
			System.out.printf("FileReader會使用預設的編碼方式%s,識別出來的字元是:%n",Charset.defaultCharset());
			System.out.println(new String(cs));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//FileReader是不能手動設定編碼方式的,為了使用其他的編碼方式,只能使用InputStreamReader來代替
		//並且使用new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 這樣的形式
		try (InputStreamReader isr = new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"))) {
			char[] cs = new char[(int) f.length()];
			isr.read(cs);
			System.out.printf("InputStreamReader 指定編碼方式UTF-8,識別出來的字元是:%n");
			System.out.println(new String(cs));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

步驟 10 : 練習-數字對應的中文

找出 E5 B1 8C 這3個十六進位制對應UTF-8編碼的漢字

步驟 11 : 答案-數字對應的中文

package stream;

import java.io.UnsupportedEncodingException;

public class TestStream {
	public static void main(String[] args) throws UnsupportedEncodingException {
//		找出 E5 B1 8C 這3個十六進位制對應UTF-8編碼的漢字 
		byte[] bs = new byte[3];
		bs[0] = (byte) 0xE5;
		bs[1] = (byte) 0xB1;
		bs[2] = (byte) 0x8C;
		
		String str  =new String(bs,"UTF-8");
		System.out.println("E5 B1 8C 對應的字元是:"+str);
	}
}

步驟 12 : 練習-移除BOM

如果用記事本根據UTF-8編碼儲存漢字就會在最前面生成一段標示符,這個標示符用於表示該檔案是使用UTF-8編碼的。

找出這段標示符對應的十六進位制,並且開發一個方法,自動去除這段標示符

步驟 13 : 答案-移除BOM

package stream;
   
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
   
public class TestStream {
   
    public static void main(String[] args) {
        File f = new File("E:\\project\\j2se\\src\\test.txt");
        try (FileInputStream fis = new FileInputStream(f);) {
            byte[] all = new byte[(int) f.length()];
            fis.read(all);
            System.out.println("首先確認按照UTF-8識別出來有?");
            String str = new String(all,"UTF-8");
            System.out.println(str);
            System.out.println("根據前面的所學,知道'中'字對應的UTF-8編碼是:e4 b8 ad");
            System.out.println("打印出檔案裡所有的資料的16進位制是:");
            for (byte b : all) {
                int i = b&0xff;
                System.out.print(Integer.toHexString(i)+ " ");
            }
            System.out.println();
            System.out.println("通過觀察法得出 UTF-8的 BOM 是 ef bb bf");
            byte[] bom = new byte[3];
            bom[0] = (byte) 0xef;
            bom[1] = (byte) 0xbb;
            bom[2] = (byte) 0xbf;
            byte[] fileContentWithoutBOM= removeBom(all,bom);
            System.out.println("去掉了BOM之後的資料的16進位制是:");
            for (byte b : fileContentWithoutBOM) {
                int i = b&0xff;
                System.out.print(Integer.toHexString(i)+ " ");
            }           
            System.out.println();
            System.out.println("對應的字串就沒有問號了:");
            String strWithoutBOM=new String(fileContentWithoutBOM,"UTF-8");
            System.out.println(strWithoutBOM);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
   
    }
 
    private static byte[] removeBom(byte[] all, byte[] bom) {
        return Arrays.copyOfRange(all, bom.length, all.length);
    }
}