11.3.4 注意問題
上面介紹了IO類的基本使用,熟悉了實體流和裝飾流的基本使用,但是在IO類實際使用時,還是會遇到一系列的問題,下面介紹一些可能會經常遇到的問題。
11.3.4.1 類的選擇
對於初次接觸IO技術的初學者來說,IO類體系博大精深,類的數量比較龐大,在實際使用時經常會無所適從,不知道該使用那些類進行編程,下面介紹一下關於IO類選擇的一些技巧。
選擇類的第一步是選擇合適的實體流。
選擇實體流時第一步是按照連接的數據源種類進行選擇,例如讀寫文件應該使用文件流,如FileInputStream/FileOutputStream、FileReader/FileWriter,讀寫字節數組應該使用字節數組流等,如ByteArrayInputStream/ByteArrayOutputStream。
選擇實體流時第二步是選擇合適方向的流。例如進行讀操作時應該使用輸入流,進行寫操作時應該使用輸出流。
選擇實體流時第三步是選擇字節流或字符流。除了讀寫二進制文件,或字節流中沒有對應的流時,一般都優先選擇字符流。
經過以上步驟以後,就可以選擇到合適的實體流了。下面說一下裝飾流的選擇問題。
在選擇IO類時,實體流是必需的,裝飾流是可選的。另外在選擇流時實體流只能選擇一個,而裝飾流可以選擇多個。
選擇裝飾流時第一步是選擇符合要求功能的流。例如需要緩沖流的話選擇BufferedReader/BufferedWriter等,有些時候也可能只是為了使用某個裝飾流內部提供的方法。
選擇裝飾流時第二步是選擇合適方向的流,這個和實體流選擇中的第二步一致。
當選擇了多個裝飾流以後,可以使用流之間的多層嵌套實現要求的功能,流的嵌套之間沒有順序。
11.3.4.2 非依次讀取流數據
由於IO類設計的特點,在實際讀取時,只能依次讀取流中的數據,而且在通常情況下,已經讀取過的數據無法再進行讀取。如果需要重復讀取流中某段數據時,一般的做法是將從流中讀取的數據使用數組存儲起來,然後根據需要讀取數組中的內容即可,但是有些時候,還是有一些特殊的情況的,IO類對於這些都進行了支持。
1、間斷性的讀取流中的數據
對於某些特殊格式的文件,例如字體文件等,在實際讀取數據時不需要順序進行讀取,而只需要根據內容的位置進行讀取。這樣可以使用流中的skip方法實現。例如:
int n = fis.skip(100);
該行代碼的作用是,以流fis當前位置為基礎,當前位置可以是流中的任何位置,向後跳過100個單位(字節流單位為字節,字符流單位是字符),如果再使用read方法繼續讀取,就是讀取跳躍以後新位置的內容,也就相當於跳過了100個單位的內容。
而實際在使用時,實際真正跳過的單位數量作為skip方法的返回值返回。
2、重復讀取流中某段數據
當必須重復讀取流中同一段數據時,如果對應的流支持mark(標記)的話,則可以重復讀取同一段數據。
下面以重復讀取控制台輸入流System.in為例子,來介紹mark的使用,示例代碼如下:
import java.io.*;
/**
* mark使用示例
*/
public class MarkUseDemo {
public static void main(String[] args) {
byte[] b = new byte[1024];
try{
//讀取數據
int data = System.in.read();
//輸出第一個字節的數據
System.out.println("第一個字節:" + data);
//判斷該流是否支持mark
if(System.in.markSupported()){
//記憶當前位置,可以從當前位置
//向後最多讀取100個字節
System.in.mark(100);
//讀取數據
int n = System.in.read(b);
//輸出讀取到的內容
System.out.print("第一次讀取到的內容:");
for(int i = 0;i < n;i++){
System.out.print(b[i] + " ");
}
System.out.println();
//回到標記位置
System.in.reset();
//重復讀取標記位置以後的內容
n = System.in.read(b);
//輸出讀取到的內容
System.out.print("第二次讀取到的內容:");
for(int i = 0;i < n;i++){
System.out.print(b[i] + " ");
}
System.out.println();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
在該示例中,首先調用System.in流中的read方法,讀取流中的第一個字節,並把讀取到的數據賦值給data,然後將讀取到的第一個字節的數據輸出出來。
然後調用System.in流中的markSupported判斷該流是否支持mark功能,如果支持的話則markSupported方法將返回true。
如果流System.in支持mark,則標記當前位置,並允許從當前位置開始最多讀取後續100個字節的數據,其實IO類內部的只讀取這些數據,而不真正從流中將這些數據刪除。
後續繼續讀取流中的數據,如果讀取的數據超過100個字節,則mark標記失效,並把讀取到的有效數據輸出到控制台。
如果需要從標記位置重復讀取已經讀取過的數據,則只需要調用流對象中的reset方法重置流的位置,使流可以回到mark的位置,如果繼續讀取的話,則從該位置開始可以向後讀取。這樣就可以從mark的位置開始,再次讀取後續的數據了。
例如在控制台輸入123456789,則該程序的執行結果是:
第一個字節:49
第一次讀取到的內容:50 51 52 53 54 55 56 57 13 10
第二次讀取到的內容:50 51 52 53 54 55 56 57 13 10
其中輸入的第一個字節是1,讀取時該字符的編碼是49,而後續的內容就是流的結構,其中流末尾的13和10是在輸入時,添加的回車和換行字符。
11.3.4.3 中文問題
由於JDK設計時,對於國際化支持比較好,所以JDK在實際實現時支持很多的字符集,這樣在進行特定字符集的處理時就需要特別小心了。
其實在進行中文處理時,只需要注意一個原則就可以了,這個原則就是將中文字符轉換為byte數組時使用的字符集,需要和把byte數組轉換為中文字符串時的字符集保持一致,這樣就不會出現中文問題了。
當然,如果不想手動實現字符串和byte數組的轉換,可以使用DataInputStream和DataOutputStream中的readUTF和writeUTF實現讀寫字符串。
11.4 總結
關於IO類的使用,還需要在實際開發過程中多進行使用,從而更深入的體會IO類設計的初衷,並掌握IO類的使用。
另外,IO類是Java中進行網絡編程的基礎,所以熟悉IO類的使用也是學習網絡編程必須的一個基礎。