前言:以下分析只針對純文本
1.FileInputStream默認的編碼方式就是文件的編碼方式
即:源文件是什么編碼方式,則利用FileInputStream默認讀取的字節數組,就是什么編碼方式。
例:純文本采用“GBK”編碼,文本內容如下(注意:文本是純漢字):
你好世界我是潘小白
利用“GBK”字符集解碼如下:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[4];//這里數組容量必須采用2的倍數,具體原因后面后談 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"GBK");//利用GBK字符集,對FileInputStream讀取的字節數組進行解碼 System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //釋放資源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: 你好世界我是潘小白 */
分析:通過代碼可知,我采用FileInputStream對格式為“GBK”的純漢字文本讀取,得到的字節數組,可以用"GBK"字符集對其完美解碼;反推可知,FileInputStream默認讀取的字節數組,其編碼格式和原文件編碼格式相同。接下來,用"UTF-8"對其進行解碼試一試。。。
利用“UTF-8”字符集解碼如下:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[4];//這里數組容量采用3的倍數,區別於上面GBK解碼時2的倍數,具體原因后面談 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"UTF-8");//利用UTF-8字符集對字節數組進行解碼 System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //釋放資源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: �������������С��//輸出無法解碼 */
分析:利用UTF-8無法解碼,再次說明,FileInputStream默認讀取的字節數組的編碼格式,就是原文件的編碼格式。
同理讀者可以將純文本(純漢字文本)設置成UTF-8的編碼格式,再分別采用“GBK”和“UTF-8”方式解碼試一試,特別注意數組容量的選擇,即:“純漢字文本,GBK解碼時,字節數組容量是2的倍數”、““純漢字文本,UTF-8解碼時,字節數組容量是3的倍數”,原因下面分析。
————————簡單的分割————————
2.采用“GBK”對純漢字文本解碼時,字節數組容量是2的倍數;“UTF-8”對純漢字文本解碼時,字節數組容量是3的倍數。
原因是:“GBK”編碼時,一個漢字是2個字節,“UTF-8”對常規漢字編碼時,一個漢字是3個字節(UTF-8方式下,生僻漢字也可能會占4個字節,這種方式此處不談)。
所以,你要對字節數組解碼時,你首先必須成組的取字節(“GBK”模式下2的倍數一組,“UTF-8”模式下3的倍數一組),否則會將一個漢字的字節拆開,這樣肯定會亂碼,其對應着我上一篇文章提到的“字節數不全或者丟失情況,產生的亂碼”。
此處,我們用代碼做一下簡單示范,原文本采用“GBK”編碼,字節數組容量采用3,不是2的倍數:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[3];//不是2的倍數 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"GBK");//卻用GBKJ解碼 System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //釋放資源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: 你�檬�界�沂�潘�“�//也就第一個字取全了,解碼出來,但是后面字節數亂了,也就無法解碼了 */
結果看到,基本全部亂碼。
同理,讀者可以采用UTF-8的文本,而設置字節數組容量不是3的倍數,從而進行UTF-8解碼,試試看;你會發現,即使編碼-解碼的字符集同步,但是字節數組中字節個數不對,同樣亂碼。
——————簡單的分割線——————
上面問題2中,“編碼-解碼的字符集同步,字節數組中字節個數不匹配出現亂碼”可以進一步延伸;
我們看到上面,都是純漢字文本,沒有任何英文字符(包括英文字母和英文標點),如果文本是,中英文混合怎么辦,還能否采用上面的方式,對FileInputStream讀取的字節進行解碼呢??
答案是:不能,見下面分析。
3.中英文混合純文本,用FileInputStream讀取時,得到的字節數組無法采用上面 String str = new String(bytes,0,length,"CharacterSet")方式解碼,應該采用字符轉換流InputStreamReader。
(提示:這里不再考慮標點符號的事了,你可以將英文標點符號看出一個英文字母,中文下的標點看成一個普通漢字分析,因為同一種編碼格式下,中文字母和中文標點占用字節數一樣,英文字母和英文標點占用字節數一致)
原因:無論是"GBK"還是"UTF-8",英文占用1個字節,所以,當插入引文時,一定會改變字節個數混亂,無法保證“在GBK格式下,每個漢字的兩個字節同時被字節數組讀取”,也無法保證“在UTF-8格式下,每個漢字的三個字節同時被字節數組讀取”,那么將導致后期解碼時,出現亂碼。
示例:文本格式是“GBK”,文本中插入了一個英文字母
你好p世界我是潘小白
代碼如下:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[2];//字節數組容量采用2 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"GBK");//GBK解碼,實現編碼-解碼格式匹配 System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //釋放資源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: 你好p�瀾縹沂橋誦“� */
結果分析:輸出結果從字母p以后,出現亂碼;這里選取得字節數組的容量是2,所以前兩個漢字被一一讀取,並完美解碼,但是讀取字母p得時候,因為其只占用一個字節,所以漢字“世”被取一個字節,留下一個字節,未被取走,所以導致“世”字無法被正確解碼,而且這也引發連鎖效應,后面得字都將被錯誤得讀取,從而亂碼。
總之:這也是亂碼得一種情況,即“字節數丟失或者不完整造成亂碼”。
這里,可能有人會有疑問,“如果將字節數組容量設置非常大,一次將中英文混合文本全部讀取,然后再解碼,這樣不出現文字多次讀取,造成漢字字節截斷得情況,不就行了嗎?”
是的,這種情況可以實現正確解碼,但是如果文本超級大,這種方式是不現實得,因為字節數組得容量過大,不現實,還是乖乖的用字符轉換流InputStreamReader吧。
下面用一個超大字節數組,將文本一次讀取,並完美解碼得代碼示例:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[20];//數組容量超級大,一次能將中英混合文本全部讀取完 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"GBK"); System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //釋放資源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: 你好p世界我是潘小白//完美解碼 */
除了上面的情況,讀者也可以試試,對於中英文結合的文檔,采用UTF-8編碼-解碼;或者故意將英文字體排布規則,即GBK格式下,2個英文一起排列,放在中文文本中,或者GBK格式下,2個英文一起排列,放在中文文本中。對其進行編碼和解碼,並分析一下原因。
補充一點:UTF-8編碼格式下,一些生僻漢字占4個字節,所以將字節數組容量設置成3的倍數時,面對有生僻字的純漢字文本,解碼時也會出現亂碼情況。
——————分割線——————
總結:
上面討論的三個問題,問題1就是屬於編碼-解碼字符集匹配問題,只是進一步說明了FileInputStream讀取的字節數組是哪種編碼方式;
問題2和3,是討論在編碼-解碼字符集匹配情況下,字節個數不完整或者丟失時,解碼時出現亂碼的情況,從而說明了用FileInputStream讀取時,得到的字節數組無法采用上面 String str = new String(bytes,0,length,"CharacterSet")方式解碼,應該采用字符轉換流InputStreamReader。