1.前言
在衆多流行的編程語言中,Java對IO的處理應該是最特殊的,Java打着“儘量減少IO類的設計理念”,搞出了目前應該是最複雜的一套IO相關類,並稱之爲Java流。
對於新手來說,Java流包含的類衆多,含義混雜,上手困難且其中暗藏的陷阱衆多;但是對於熟悉了Java流的程序員來說,它的確稱得上功能強大。
本文總結了一些Java流的使用指南,給出了一些實例代碼,主要內容包括:
- Java流中的字節與字符
- 文件流
- 字節數組流
- 管道流
- 緩衝流
- 數據流
- 壓縮流
- 摘要流
- 加密流
- 多重流使用範例
本文的讀者應該是Java程序員,最好具有基本的Java基礎知識。本文給出的代碼都已經在Jdk7上運行通過,如有錯誤,請及時反饋。
2.Java中的字節與字符
2.1Java流概論
Java流是Java語言用來處理IO的一套類,針對字節進行處理的類被命名爲stream結尾,例如FileInputStream、FileOutputStream;針對字符進行處理的類被命名爲Reader或Writer結尾,例如FileReader或者FileWriter。要弄清Java流,首先要明白Java中的字節與字符的區別。
包含InputStream或Reader的類被稱爲輸入流,此處的“輸入”是指從外部(文件、網絡、其他程序)將信息輸入到Java程序內部;或者指從Java程序內部的某個變量輸入到當前操作的代碼塊。
包含OutputStream或Writer的類被稱爲輸出流,此處的“輸出”是指從Java程序內部將信息輸出到外部(文件、網絡、其他程序);或者指從當前操作的代碼塊將信息輸出到其他變量。
2.2 字節(byte)的取值
一個字節包含一個8位的二進制數字,在Java中使用byte來表示。最大值爲127,最小值爲-128。下面的代碼列出了所有byte的值:
public static void listAllByteValue(){
for (int i = -128; i < 128; i++) {
byte b = (byte) i;
System.out.println("byte:" + b + ",Binary:" + byteToBinaryString(b));
}
}
public static String byteToBinaryString(byte b) {
String s = Integer.toBinaryString(b);
if (b < 0) {
s = s.substring(24);
} else {
if (s.length() < 8) {
int len = s.length();
for (int i = 0; i < 8 - len; i++) {
s = "0" + s;
}
}
}
return s;
}
大於等於0的byte值,使用其原碼錶示,即直接存儲該byte的二進制值;小於0的byte值,使用其補碼錶示,即將該值的絕對值的原碼按位取反再加1。例如42就存儲爲00101010;而-42先求42的值00101010,然後按位取反得到11010101,再加1得到11010110,此即爲-42的二進制值。
2.3 字節(byte)的賦值
byte可以用多種方法來賦值,見下列代碼:
public static void byteGetValue(){
//二進制以0b開頭
byte b1 = 0b00101010;
System.out.println("b1:"+b1+",Binary:"+byteToBinaryString(b1));
//八進制以0開頭
byte b2 = 052;
System.out.println("b2:"+b2+",Binary:"+byteToBinaryString(b2));
//十進制
byte b3 = 42;
System.out.println("b3:"+b3+",Binary:"+byteToBinaryString(b3));
//十六進制
byte b4 = 0x2a;
System.out.println("b4:"+b4+",Binary:"+byteToBinaryString(b4));
//-42的賦值
//二進制,由於11010110以原碼來理解已經超過了127,因此必須使用byte進行強制類型轉換
byte b5 = (byte) 0b11010110;
System.out.println("b5:"+b5+",Binary:"+byteToBinaryString(b5));
//八進制以0開頭
byte b6 = -052;
System.out.println("b6:"+b6+",Binary:"+byteToBinaryString(b6));
//十進制
byte b7 = -42;
System.out.println("b7:"+b7+",Binary:"+byteToBinaryString(b7));
//十六進制,由於0xd6以原碼來理解已經超過了127,因此必須使用byte進行強制類型轉換
byte b8 = (byte) 0xd6;
System.out.println("b8:"+b8+",Binary:"+byteToBinaryString(b8));
//將兩個int轉爲byte的示例,示例告訴我們int轉byte,就是簡單的截取最後8位
int i1 = 0b001011010110;
int i2 = 0b110011010110;
System.out.println("i1 = "+i1+", i2 = "+i2);
byte b9 = (byte) i1;
byte b10 = (byte) i2;
System.out.println("b9 = "+b9+", b10 = "+b10);
}
值得注意的是,當int轉換爲byte時,直接截取了int的後8位。
運行結果:
b1:42,Binary:00101010
b2:42,Binary:00101010
b3:42,Binary:00101010
b4:42,Binary:00101010
b5:-42,Binary:11010110
b6:-42,Binary:11010110
b7:-42,Binary:11010110
b8:-42,Binary:11010110
i1 = 726, i2 = 3286
b9 = -42, b10 = -42
2.4 字符(char)的取值
由上面的例子可知byte就是一個單純的8位二進制數字,它可以有多種賦值方法,但是在內存中始終不變。
字符在Java中使用char基本類型來存儲,它是一個16位的unicode碼,也可以理解爲一個16位的二進制數字,其取值範圍爲0到65535(2的16次方-1)。下面的例子給出了一段中文的char值:
public static void listCharValue() {
System.out.println("char max value is :"+(int)Character.MAX_VALUE+", min value is :"+(int)Character.MIN_VALUE);
for (char c = 19968; c < 20271; c++) {
System.out.print(c);
}
}
運行結果:
char max value is :65535, min value is :0
一丁丂七丄丅丆萬丈三上下丌不與丏丐醜丒專且丕世丗丘丙業叢東絲丞丟丠両丟丣兩嚴並喪丨丩個丫丬中丮丯豐丱串丳臨丵丶丷丸丹爲主丼麗舉丿乀乁乂乃乄久乆乇麼義乊之烏乍乎乏樂乑乒乓喬乕乖乗乘乙乚乛乜九乞也習鄉乢乣乤乥書乧乨乩乪乫乬乭乮乯買亂乲乳乴乵乶乷乸乹乺乻乼乽乾乿亀亁亂亃亄亅了亇予爭亊事二亍於虧亐雲互亓五井亖亗亙亙亞些亜亝亞亟亠亡亢亣交亥亦產亨畝亪享京亭亮亯亰亱親亳亴褻亶亷嚲亹人亻亼亽亾億什仁仂仃仄僅僕仇仈仉今介仌仍從仏仐侖仒倉仔仕他仗付仙仚仛仜仝仞仟仠仡仢代令以仦仧仨仩儀仫們仭仮仯仰仱仲仳仴仵件價仸仹仺任仼份仾仿伀企伂伃伄伅伆伇伈伉伊伋伌伍伎伏伐休伒伓伔伕伖衆優夥會傴伜伝傘偉傳俥伢俔傷倀倫傖伨伩僞佇伬伭伮
2.5 字符(char)的賦值
後面會提到,char可以表現爲世界各國的各種字符,但是在內存中,它就是一個16位的二進制數字,因此其賦值方法也與byte一樣多種多樣。
public static void charGetValue() {
//把'一'賦值給char
char c1 = '一';
System.out.println(c1);
char c2 = 0b100111000000000;
System.out.println(c2);
char c3 = 047000;
System.out.println(c3);
char c4 = 19968;
System.out.println(c4);
char c5 = 0x4e00;
System.out.println(c5);
char c6 = '\u4e00';
System.out.println(c6);
int i1 = (int) '一';
System.out.println(Integer.toHexString(i1));
}
運行結果:
一
一
一
一
一
一
4e00
2.6 char轉換爲byte
char存儲的是字符,在很多情況下它需要被轉換爲字節,例如存儲到文件中時,或者在網絡上進行傳遞時。當字符轉換爲byte時,需要用到編碼格式,例如GBK或者unicode或者UTF-8等等,不同的編碼格式轉換得到的byte數組也不一樣,如下圖所示:
關於編碼格式的詳細介紹,可以去搜索其他文章,簡要說一下:
Java內部使用Unicode作爲char的編碼格式,它又分爲UCS-2(每兩個字節代表一個char)和UCS-4(每四個字節代表一個char),目前主流的都是UCS-2。當把字符或者字符串轉換爲unicode時,會在頭部加入兩個字節的標誌位(FE FF)表示big Endian(字節序,內存低位地址存放最高有效字節)。
UTF-8編碼:一個英文字符佔一個字節,一個漢字佔三個字節;
GBK編碼:一個英文字符佔一個字節,一個漢字佔兩個字節。
代碼如下:
public static void charToBytes() {
try {
byte[] buf1 = "一".getBytes("unicode");
System.out.println("---------unicode---------");
for (int i = 0; i < buf1.length; i++) {
System.out.println(Integer.toHexString(buf1[i]));
}
System.out.println("---------UTF-8---------");
byte[] buf2 = "一".getBytes("UTF-8");
for (int i = 0; i < buf2.length; i++) {
System.out.println(Integer.toHexString(buf2[i]));
}
System.out.println("---------UTF-16---------");
byte[] buf3 = "一".getBytes("UTF-16");
for (int i = 0; i < buf3.length; i++) {
System.out.println(Integer.toHexString(buf3[i]));
}
System.out.println("---------gbk---------");
byte[] buf4 = "一".getBytes("gbk");
for (int i = 0; i < buf4.length; i++) {
System.out.println(Integer.toHexString(buf4[i]));
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public static String byteToHexString(byte b) {
String s = Integer.toHexString(b);
int len = s.length();
if (len >= 2) {
s = s.substring(len - 2);
}else{
s = "0"+s;
}
return s;
}
運行結果:
---------unicode---------
fffffffe
ffffffff
4e
0
---------UTF-8---------
ffffffe4
ffffffb8
ffffff80
---------UTF-16---------
fffffffe
ffffffff
4e
0
---------gbk---------
ffffffd2
ffffffbb
2.7 byte轉換爲char
當Java程序從外部(文件、網絡、其他應用程序)讀入字節流時,若該字節流代表的是字符,則需要將字節轉換爲字符,此動作被稱爲轉碼。轉碼時需要注意其編碼格式,例子如下:
public static void byteToChar() {
byte[] unicode_b = new byte[4];
unicode_b[0] = (byte) 0XFE;
unicode_b[1] = (byte) 0XFF;
unicode_b[2] = (byte) 0X4E;
unicode_b[3] = (byte) 0X2D;
String unicode_str;
byte[] gbk_b = new byte[2];
gbk_b[0] = (byte) 0XD6;
gbk_b[1] = (byte) 0XD0;
String gbk_str;
byte[] utf_b = new byte[3];
utf_b[0] = (byte) 0XE4;
utf_b[1] = (byte) 0XB8;
utf_b[2] = (byte) 0XAD;
String utf_str;
try {
unicode_str = new String(unicode_b, "unicode");
System.out.println("unicode string is:" + unicode_str);
gbk_str = new String(gbk_b, "gbk");
System.out.println("gbk string is:" + gbk_str);
utf_str = new String(utf_b, "utf-8");
System.out.println("utf-8 string is:" + utf_str);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
運行結果如下:
unicode string is:中
gbk string is:中
utf-8 string is:中
2.8 轉碼和亂碼
將一種編碼格式轉換爲另一中編碼格式稱之爲轉碼。Java內部使用unicode來進行字符編碼,當將字符轉換爲byte時需要轉碼,轉爲GBK或者其他編碼;當從外部讀入一段byte數組時,也需將其他編碼轉換爲unicode編碼的字符。
轉碼發生的地方只有兩個:從char到byte,或者從byte到char。轉碼發生時必須指定編碼格式,如果不指定編碼格式,則會使用默認的編碼格式(一是IDE的環境中指定的格式,二是操作系統默認的編碼格式),你可以用如下代碼獲取默認的編碼格式:
private static void showNativeEncoding() {
String enc = System.getProperty("file.encoding");
System.out.println(enc);
}
如果在轉碼時使用了錯誤的編碼格式,則會出現亂碼。
未完待續……