Java IO詳解


  一、概覽

  “流”(stream)有方向:流進(input stream)和流出(output stream)。

  “流”有流動的最小單位:①有基於一個字節(single-byte)流動的InputStream和OutputStream家族;②也有基於兩個字節流動(two-byte)的Reader和Writer家族。

  

  為什么會有兩大家族呢?

  1、基於single-byte流動的有兩個最基本的抽象類(abstract classes):InputStream和OutputStream。稍后我們會看到以這兩個抽象類作為父類,衍生了一個龐大的IO家族。

  2、由於基於single-byte的流不方便處理那些用Unicode編碼方式存儲的字符characters信息。從而java的IO系統中又出現了另外的一個基於Reader和Writer抽象類,用於處理characters信息的家族。

  

  二、讀寫bytes

  抽象類InputStream中有一個抽象讀方法:

abstract int read();

  每次調用這個方法就會從流中讀取一個byte並返回讀取到的byte值;如果遇到輸入流的末尾,則返回-1。

  這個抽象類還重載了其它的read方法,但都是在底層調用了上面這個讀取單字節的抽象的read()方法。該抽象類還有如下方法:

  ①、abstract int read();

  ②、int read(byte[] b),最大讀取b.length個字節數據;

  ③、int read(byte[] b, int off, int len),最大讀取len個字節數據到b字節數組中,從off位置開始存放;

  ④、long skip(long n),在輸入流中跳過n個字節,返回實際跳過的字節數。當遇到末尾的時候實際跳過的數據可能小於n;

  ⑤、int available(),返回在不阻塞的情況下流中的可以讀取的字節數;

  ⑥、void close(),關閉流;

  ⑦、void mark(int readlimit),在輸入流的當前位置打一個標記(注:不是所有的流都支持這一特性);

  ⑧、void reset(),返回到最后一個標記處。隨后調用read方法會從最后一個標記處重新讀取字節數據。如果當前沒有標記,則不會有任何變化;

  ⑨、boolean markSupported(),判斷當前流是否支持標記操作;

 

  對應的,抽象類OutputStream中也有一個抽象的寫方法:

abstract void write(int b);

  OutputStream類有如下方法:

  ①、abstract void write(int b);

  ②、void write(byte[] b),將b中存放的所有數據都寫入到流中;

  ③、void write(byte[], int off, int len),將b字節數組中從off位置開始的len個字節數據寫入到流中;

  ④、void close(),關閉和flush輸出流;

  ⑤、void flush,對輸出流做flush操作,也就是說,將所有輸出流中緩存的數據都寫入到實際的目的地;

 

  上面抽象的read()和write()方法都會阻塞,直到byte讀寫成功為止。這就意味着,如果在讀寫過程中,如果當前流不可用,那么當前線程就會被阻塞。為解決阻塞的問題InputStream類提供了一個avaliable()方法,可以檢測當前可讀的字節數。所以,下面這段代碼永遠不會被阻塞:

int bytesAvailable = in.available();
if(bytesAvailable > 0){
     byte[] data = new byte[bytesAvailable];
     in.read(data);         
}

  當我們讀寫完畢以后,應該要調用close()函數來關閉流。這樣做,一方面可以釋放掉流所持有的系統資源。另外一方面,關閉一個輸出流也會將暫存在流中的數據flush到目標文件中去:輸出流會持有一個buffer,在其buffer沒有滿的時候是不會實際將數據傳遞出去的。特別的,如果你沒有關閉一個輸出流,那么很有可能會導致最后那些存放在buffer中的數據沒有被實際的傳遞出去。當然,我們也可以通過調用flush()方法手動的將buffer中的數據flush出去。

   

  三、結合stream filters

  先來看一下第一個家族:

  什么叫Combining Stream Filter呢?我們逐一的解釋。

  我們從第一個層面上看(直接繼承自InputStream或OutputStream的這些類),FileInputStream能夠讓你得到一個附着在磁盤文件上的輸入流,FileOutputStream能夠得到一個對磁盤文件的輸出流。比如用下面的方式:

FileInputStream fin = new FileInputStream("employee.dat");
FileOutputStream fout = new FileOutputStream("employee.dat");

  和InputStream、OutputStream抽象類一樣,FileInputStream和FileOutputStream也只提供基於byte的讀寫方法

  但是,我們如果能夠得到一個DateInputStream,那么我們就可以從流中讀取numeric types了,比如我們可以從流中讀取一個double類型的數據:

DataInputStream din = ...
double s = din.readDouble();

  現在我們可以YY一下,要是能夠直接向file中讀寫numeric types該多好!!!你當然可以做得到,就像下面這樣:

FileInputStream fin = new FileInputStream("employee.dat");
DataInputStream din = new DataInputStream(fin);
Double s = din.readDouble();

  看,你做到了。只要將兩個層面上的流結合起來,就可以了。java使用了一種很好的機制將對底層和對上層的操作分開,這樣既方便了流向底層寫byte,也方便了我們使用我們習慣的numeric types類型。

  再介紹一對很重要的流,它對提高讀寫效率有很大的幫助:BufferedInputStream和BufferedOutputStream,他們分別為輸入和輸出流提供了一個緩沖區。比如在上面的流中添加一個緩沖區,讓它更快一些:

FileInputStream fin = new FileInputStream("employee.dat");
BufferedInputStream bin = new BufferedInputStream(fin);
DataInputStream din = new DataInputStream(bin);
Double s = din.readDouble();

  有了上面的分層介紹以后,你當然會很明白為什么要將BufferedInputStream放在中間層,而不是很殺馬特的將其放在最外層了。你可知道,BufferedInputStream和BufferedOutputStream只提供對byte的讀寫方法。還有以下兩個例子:

復制代碼
//1、可以利用pin.unread(b)來跳躍,利用din.readLong()等讀取numeric types
PushbackStream pin = null;
DataInputStream din = new DataInputStream(
      pin = new PushbackStream(new FileInputStream("employee.dat")));

//2、對zip的操作
ZipInputStream zin = new ZipInputStream(new FileInputStream("employee.dat"));
DataInputStream din = new DataInputStream(zin);
復制代碼

  理解到這里,我們可以放心的相信一件事情了:關閉流的時候,只需要關閉最外層的流即可。因為,它自己會一層一層的往里面調用close()方法。

  

  四、讀寫character

  字符相對來說比java基本類型的數據難處理。我們知道,字符有很多種編碼方式。比如,ASCII編碼占用1個字節長度,每個Unicode占用2個字節長度。為了方便處理文本形式的流,JDK單獨開辟了另外一個專門的IO家族——Reader和Writer。類似於前面的InputStream和OutputStream,這兩個類分別有一個抽象的讀/寫方法:

abstract int read();  //返回一個0~65535之間的整數,遇到流末尾則返回-1。
abstract void write(int c);

 

  五、讀寫文本(text)

  當你向保存一個數據的時候,你有兩種選擇保存數據的方式:二進制和文本格式。比如說,整數1234用二進制保存的時候,它是這樣的 00 00 04 D2(in hex);如果采用文本格式,則它會被保存為字符串“1234”的形式。

  盡管,對二進制數據的讀寫很快速而且高效,但是二進制不方便於人的閱讀。當我們保存一個一個文本字符串的時候,我么需要考慮到字符的編碼方式。如果用UTF-16的編碼方式,則“1234”將會保存為 00 31 00 32 00 33 00 34(in hex);而采用ISO8859-1編碼,則會保存為 31 32 33 34(in hex)。舉個例子:

InputStreamReader in = new InputStreamReader(System.in);

這個InputStreamReader會將從控制台讀取到的數據用系統默認的編碼方式進行編碼。當然,也可以用InputStreamReader(new FileInputStream("kernel.dat"),"ISO8859_5")的方式明確指定哪種編碼方式。

  因為,我們有很多地方需要將一個file綁定到reader或者是writer上面;所以,JDK給我們提供了一對方便的讀寫類FileReader和FileWriter。比如說下面兩種定義是等價的:

//方便的定義方式
FileWriter out = new FileWriter("output.txt");

//等價的定義方式
FileWriter out = new FileWriter(new FileOutputStream("output.txt"));

  

  1、怎樣寫Text

  對於文本的輸出,有一個方便的類PrintWriter。因為,這個類提供了文本格式的寫字符串和寫數字的方法,其print方法有很多種重載方式。同時,我們還可以很方便的將PrintWriter和FileWriter聯系起來,下面的兩種方式是等價的:

復制代碼
//定義PrintWriter的便捷方式
PrintWriter out = new PrintWriter("out.txt");

//等價的定義方式
PrintWriter out = new PrintWriter(new FileWriter("out.txt"));

//聯想到FileWriter我們還可以得出一種等價方式
PrintWriter out = new PrintWriter(new FileWriter(new FileOutputStream("out.txt")));
復制代碼

  還需要注意的一點就是,PrintWriter自帶了一個緩沖器,默認情況下只有在緩沖區填滿的時候才會將數據flush到目的地。PrintWriter的構造器有兩種:

//默認情況下 autoFlush是關閉的,緩沖區慢才會將數據傳遞出去
PrintWriter out = new PrintWriter(Writer out);

//可以指定autoFlush為true。這樣,無論何時調用print函數,都會立刻flush緩沖區
PrintWriter out = new PrintWriter(Writer out, boolean autoFlush);

  注意,PrintWriter有如下的構造函數:

復制代碼
PrintWriter(Writer out)
PrintWriter(Writer out, boolean autoFlush)

PrintWriter(String fileName)
PrintWriter(File file)

//這個很強大,可以直接對輸出流做打印
PrintWriter(OutputStream out)
PrintWriter(OutputStream out, boolean autoFlush)

//還有一個很有意思的printf函數。這個對調整格式很方便
void printf(String format, Object... args)
復制代碼

 

  2、怎樣讀Text

  如我們所知道的,對二進制數據的讀寫很方便的可以使用DataInputStream和DataOutputStream對。上面也說了,寫Text有一個很好用的PrintWriter。那么,讀Text呢?還會有想二進制這么方便嗎?比如說,我想讀取一個Double類型的數據: r.readDouble()。答案:不好意思,沒有!!

  就目前來講,有兩種方式:①、Scanner類可用,也提供了不少方法;②、BufferedReader in = new BufferedReader(new FileReader("employee.txt"));可用,用它來讀取一行,然后自行分解去吧。但是,BufferedReader么有讀取numeric這么方便的方法。

  其實,也可想而知,文本嘛,就沒有所謂的Double啊,Integer啊什么的區別了,所有的都是“文本”了,只是它長得像數字罷了。

 

   最后,來看一下Writer和Reader家族:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM