菜鳥舉例了解位元組流和字元流差別
按照uft8編碼方式存儲文檔
文檔存儲路徑在D盤下
/**
* 按照utf8格式存儲文檔
*/
public static void storeDataByUTF8(){
String path = "D:" + File.separator + "textutf8.txt";
File file = new File(path);
try {
PrintWriter pw = new PrintWriter(file,"utf-8");
for(int i=;i<;i++){
pw.write(i+":"+"位元組流與字元流!!!"+"\r\n");
}
pw.flush();
pw.close();
} catch (FileNotFoundException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
對比使用BufferedReader和FileInputStream按照byte讀取文檔(按照位元組流方式讀取)
使用BufferedReader按照byte讀取文檔代碼如下:
public static void readDataWithArray(){
String path = "D:" + File.separator + "textutf8.txt";
File file = new File(path);
try{
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
BufferedReader in = new BufferedReader(isr);
byte[] b = new byte[];
int temp = ;
int len = ;
while((temp=in.read())!=-){ // -1是結束符
b[len] = (byte)temp;
if(len<)
len++;
else
break;
}
in.close();
System.out.println(new String(b,,len,"utf-8"));
}catch(Exception e){
e.printStackTrace();
System.out.println("While reading the data, the program threw out exceptions");
}
}
輸出結果發生了亂碼:
:W?AW&A
:W?AW&A
:W?AW&A
:W?AW&A
:W?AW&A
FileInputStream按照byte讀取文檔代碼如下:
public static void readDataWithArray(){
String path = "D:" + File.separator + "textutf8.txt";
File file = new File(path);
try{
FileInputStream in = new FileInputStream(file);
//InputStreamReader isr = new InputStreamReader(fis,"utf-8");
//BufferedReader in = new BufferedReader(isr);
byte[] b = new byte[];
int temp = ;
int len = ;
while((temp=in.read())!=-){ // -1是結束符
b[len] = (byte)temp;
if(len<)
len++;
else
break;
}
in.close();
System.out.println(new String(b,,len,"utf-8"));
}catch(Exception e){
e.printStackTrace();
System.out.println("While reading the data, the program threw out exceptions");
}
}
輸出結果正常:
:位元組流與字元流!!!
:位元組流與字元流!!!
:位元組流與字元流!!!
:位元組流與字元流!!!
:位元組流與字元流!!!
以上兩段代碼不同之處隻是讀取文本的流不同,其他代碼相同。要回答這個問題,首先來了解一下為什麼要編碼?
為什麼要編碼
不知道大家有沒有想過一個問題,那就是為什麼要編碼?我們能不能不編碼?要回答這個問題必須要回到計算機是如何表示我們人類能夠了解的符号的,這些符号也就是我們人類使用的語言。由于人類的語言有太多,因而表示這些語言的符号太多,無法用計算機中一個基本的存儲單元—— byte 來表示,因而必須要經過拆分或一些翻譯工作,才能讓計算機能了解。我們可以把計算機能夠了解的語言假定為英語,其它語言要能夠在計算機中使用必須經過一次翻譯,把它翻譯成英語。這個翻譯的過程就是編碼。是以可以想象隻要不是說英語的國家要能夠使用計算機就必須要經過編碼。
是以總的來說,編碼的原因可以總結為:
- 計算機中存儲資訊的最小單元是一個位元組即 8 個 bit,是以能表示的字元範圍是 0~255 個
- 人類要表示的符号太多,無法用一個位元組來完全表示
- 要解決這個沖突必須需要一個新的資料結構 char,從 char 到 byte 必須編碼
為什麼兩者的結果那麼不同呢?
在Java中byte是1個位元組(8個bit),char是2個位元組;
最基礎的流InputStream和OutputStream是按照位元組讀取的(byte)。
/**
* Reads a byte of data from this input stream. This method blocks
* if no input is yet available.
*
* @return the next byte of data, or <code>-</code> if the end of the
* file is reached.
* @exception IOException if an I/O error occurs.
*/
public int read() throws IOException {
return read0();
}
但是BufferedReader類的read()方法傳回的是char。是以沒有處理好編碼原因的第三個原因。
public class BufferedReader extends Reader {
private char cb[]; // char數組的緩存
private int nChars, nextChar;
...
/**
* Reads a single character.
*
* @return The character read, as an integer in the range
* 0 to 65535 (<tt>0x00-0xffff</tt>), or -1 if the
* end of the stream has been reached
* @exception IOException If an I/O error occurs
*/
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
for (;;) {
if (nextChar >= nChars) {
fill();
if (nextChar >= nChars)
return -;
}
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
continue;
}
}
return cb[nextChar++];
}
}
}
}
舉個詳細的例子:
按照 ISO-8859-1 編碼字元串“I am 君山”用 ISO-8859-1 編碼,下面是編碼結果:
從上圖看出7個char 字元經過ISO-8859-1 編碼轉變成7個byte 數組,ISO-8859-1是單位元組編碼,中文“君山”被轉化成值是3f的byte。3f也就是“?”字元,是以經常會出現中文變成“?”很可能就是錯誤的使用了 ISO-8859-1這個編碼導緻的。中文字元經過 ISO-8859-1編碼會丢失資訊,通常我們稱之為“黑洞”,它會把不認識的字元吸收掉。由于現在大部分基礎的 Java 架構或系統預設的字元集編碼都是 ISO-8859-1,是以很容易出現亂碼問題。
以上總結隻是舉例了一個簡單的例子,希望能通過這個例子使得你對Java位元組流和字元流的差別有個感性的認識。如果需要了解兩者的差別,強烈建議大家看看另一篇部落格《深入分析 Java 中的中文編碼問題》。
如果部落格寫得有什麼問題,請各請各位大神指出。