何謂串流?

串流就如同一條水管一樣,要輸入端有水注進來,輸出端就會有水流出,輸入端注入多少的水,輸出端就會收到同等的水量,所以只要輸入端的水量足夠,輸出端就可以無限制的一直有水流出;而串流也就是採用相同的原理,只是把水管換成了傳輸線,而水就成為了數據資料,資料的輸入端可以是有限長度或,或是無限長度的資料來源,而對輸出端而言,資料串流(Stream)就像是一連串具有順序且無法得知長度的位元組,輸出端可以選擇將資料接收儲存下來,或者隨它流逝,但這些流逝沒被儲存下的資料之後若有需要就無法在重新找回來,得在叫輸入端重新傳送一次。如下圖所示,在Java中資料的來源端可以是各式各樣的輸入裝置,而程式的輸出也可以送到許多不同的目的地上,對於資料來源與目的端各式不同的裝置,透過串流的概念可以簡單的提供一套標準的抽像方法,讓資料的輸入輸出更加的容易被理。

2

圖 41 基本串流概念圖

在這一套標準的抽像方法上,我們可以依照需求來實作各式不同的串流資料處理裝置,例如可以過濾特定資料的過濾串流、針對檔案讀寫的檔案串流,或是針對物件讀寫的物件串流。而且不同的串流裝置彼此之間可以互相的連接成一條串流鏈。這就如同一套自來水過濾裝置,藉由水管將水導到不同的處理裝置中,有的可能負責過濾,有的負責消毒,經過一連串的處理過產最後產出一杯乾淨的水(如同產出我們想要的資料),以下的範例將System.in串接至不同的串流處理裝置,將原本不具Buffer,每次讀取只能以byte為單位的的InputStream串接成具有Buffer且一次可以讀取一行使用者輸入資料的BufferedReader。

【範例程式】

//IOTest.java 串流鏈範例

【執行結果】

2

圖 42 串流鏈範例輸出結果

下圖將更詳細的說明不同功能的的串流處理裝置彼此之間是如何鏈結運作。

串流串接圖

圖 43 串流鏈運作圖

所以Java在資料串流的處理方面不只提供了相當完整的流串處理類別(存在於Java.io套件內),同時也提供了相當彈性靈活的抽像介面提供使用者可以自行實作所需的串流裝置,

System.in、System.out與System.err類別

在System類別中提供了三個不同的資料串流,分別是System.in、System.out與System.err,其中System.in是InputStream類別型態的物件,其它兩者則是printStream類別型態的物件,這三個串流是大多數的Java程式設計師最先會接觸到的基本輸出輸入串流,System類別所提供的輸出入串流源,皆是來至java操控台(consol),通常就是執行Java應用程式時的操控視窗。使用System.in可以從Java操控台讀入資料(一般資料源為鍵盤),而System.out與System.err則可以輸出資料到操控台畫面上(一般為顯示器),其中System.err主要是用於系統錯誤輸出。下表詳細的說明了InputStream與PrintStream的繼承架構與System類別的關係:

類別名稱 繼承架構 被使用於
InputStream java.lang.Object System.in
PrintStream java.lang.Object System.out

到這裡我們可以知道先前幾章所使用的範例中經常使用到的System.out.println或System.out.print方法的提供者其實並不是System類別,而是來至存在於System類別中的PrintStream類別。println與print方法其實相當類似,差別只在於prinln在輸出後會自動的換行,而print則不會。print方法除了可以輸出一個字串外,同時也接受boolean、char、int、long、float、double..等各種不同的資料型態,如下面的簡單範例所示:

【範例程式】

//SystemOut.java System.out輸出範例

【執行結果】

2

圖 44 SystemOut範例執行結果

同樣的道理System.in也是使用InputSream類別所提供的read方法來讀取資料,read方法可以一次讀取一個位元組,也可以利用陣列一次讀入數個位元組。下面為一個範例利用System.in讀取使用者利用鍵盤所輸入資料。

【範例程式】

//SystemIn.java System.in輸入範例

【執行結果】

2

圖 45 SystemIn範例執行結果

輸入與輸出串流

InputStream與OutputStream這兩個抽像介面,可以說是Java所有輸出入串流的基礎來源,它定義了最基本的串流操作方法,包括串流資料讀取寫入、檢查尚有多少位元組可讀取、出清與關閉串流、跳過部份位置的串流資料、與標記串流中的特定位置各種方法。下表為 InputStream所提供的方法:

傳回值 方法名稱 說明
int available() 檢查串流中還有多少資料可以被讀取。
void close() 關閉串流,並釋放相關的系統資源。
void mark(int readlimit) 標記現在讀取的串流位置。
boolean markSupported() 測試串流是否有支援標記功能。
int read() 讀取一個byte,如果串流為空將傳回-1。
int read(byte[] b) 讀取一個byte陣列,並傳為實際讀取數量。
int read(byte[]b, int off,int len) 讀取一個byte陣例,從off位置開始讀取len長度的資料,並傳回實際讀取的資料長度。
void reset() 重新回到前一次標記的串流位置
long skip(long n) 略過n個位元。

InputStream提供read()方法一次可以讀取一個byte或是一組bytes(byte array),提外還提供基本的串流操作方法,像關閉串流、檢查串流資料、跳過串流資料等。同樣的OutputStream也提供了許多將資料寫出串流的方法,如下表:

傳回值 方法名稱 說明
void close() 關閉輸出串流,並釋放相關資源。
void flush() 將串流buffer中的資料全部寫出。
void write(int b) 寫出一個byte至串流中。
void write(byte[] b) 寫出一組byte至串流中。
void write(byte[] b, int off, int len) 寫出一組bytes,從off位置開始寫出len長度的資料至串流中。

  OutputStream除了提供關閉串流與輸出資料的方法外,另外還有一個比較特別的flush()方法。有時我們呼叫write()方法所寫出資料,資料可能不會立即的被送出至串流上,而是會先暫存在串流的Buffer之中,等到達到一定的資量後,才會一次被送出至串流上,這麼做是為使資料的輸出更有效率。若不想輸出的資料被放置於buffer中等待,這時就可以利用flush()方法,手動將Buffer中的資料全部輸出到串流上。

Java除了提供這兩個輸出入位元資料串流外(byte stream),對於字元型態的資料傳輸,Java也提供了字元資料串流(character stream)可供選擇,分別是Reader與Writer類別。Reader類別一樣是利用read()方法讀取串流資料,只不過所讀取的資料會自動的為我們轉換成為字元型別,Reader所提供的方法如下表所示:

傳回值 方法名稱 說明
abstract void close() 關閉串流。
void mark(int limit) 標記現在的串流位置,limit為長度限制。
boolean markSupported() 檢查是否支援標記功能。
int read() 讀取一個字元。
int read(char[] cbuf) 讀取一個字元陣列。
abstract int read(char[] cbuf, int off, int len) 從off位置開始讀取長度len的字元,並傳回實際讀取的字元長度。
boolean ready() 詢問串流是否己有準備好資料可以被讀取。
void reset() 回到串流標記的位置。
long skip(long n) 略過n個字元。

Writer類別則提供write()方法可以寫出一個單一的字元或由一連串的字元所組成的字串,Writer類所提供的方法如下表:

傳回值 方法名稱 說明
abstract void close() 關閉串流。
abstract void flush() 將串流buffer中的資料全部寫出。
void write(char[] cbuf) 輸出一組字元陣列。
abstractvoid write(char[] cbuf,int off,int len) 從cbuf字元陣列的off位置開始輸出長度為len的元字。
void write(int c) 寫出一個字元。
void write(String str) 寫出一個字串。
void write(String str, int off, int len) 從str字串的off位置開始寫出長度len的字串。

由於InputStream與OutputStream本身都是抽像的介面,因此並不能直接拿來使用,InputStream的資料來源可以是檔案(File)、位元陣列(byte array)、網路(internet)、物件(Object)、字串(String)或是其它的串流(Stream),而OutputSteram的輸出目的可以是檔案(File)、位元陣列(byte array)、物件(Object)或是其它串流,針對這些不同的資料輸入與輸出源,在Java函式庫中提供了不少實用的串流處理類別,而這些類最終都是實做至InputStream或OutputStream介面,倘若這些類別還是無法符合你的需求,也可以利用InputStream抽像介面實作一個符合需求的中流資料處理類別。下表為幾個較常用的串流處理類別。

位元串流 字元串流 用途
AudioInputStream 音源串流,讀取音源資料。
ByteArrayInputStream 位元陣列串流,讀寫記憶體或陣列中的資料。
FileInputStream FileReader 檔案串流,讀寫檔案中的資料。
FilterInputStream FileReader 過濾串流,可用來做串流資料轉換。
ObjectInputStream 物件串流,可以將物件序列化輸出至串流上,再將序列化的物件資料從輸入串流重新組合成為物件。
PipedInputStream 管線串流,透過串流串流可以將兩個不同的輸入與輸出串流連接起來,常利用來讓執行緒彼此可以互相溝通。
BufferedInputStream BufferedReader 具暫存的資料串流,可將資料暫存於記憶體上,以增加讀寫時的效率。
PrintStream PrintWriter 列印串流,最常使用在將資料輸出顯示於螢幕上,使用PrintStream輸出資料時flush方法將會自動的被呼叫,並且支援多種型態的資料輸出。
DataInputStream 資料串流,提供針對Java基本型別的輸出輸入操作方法,包括int、char、long、boolean等java基本型別的資料讀寫方法。
SequenceInputStream 可將數個輸入串流結合成一個單一的輸入串流,透過單一的輸入串面可依序讀取不同串流中的資料。

對於幾個比較常用的串流處理別,底下將以個別獨立的範例程式來做說明。

  • 檔案串流(FileInputStream、FileOutputStream)

【程式說明】

底下範例為一個簡單的檔案複制程式,可以將使用者指定的檔案內容複製至另一個檔案之中。

【範例程式】

//FileCopy.java 檔案複製範例

【執行結果】

2

圖 46 檔案複製範例

  • 物件串流(ObjectInputStream、ObjectOutputStream)

【程式說明】

此範例程式為一個簡單的名片簿本程式,利用物件串流可以將名片簿物件寫入檔案中,當需要輸入名片資料時,再從檔案中讀取出來轉換成物件。

【範例程式】

//ObjectIO.java 物件串流範例

【執行結果】

2

圖 47 物件串流範例執行結果。

  • 位元陣列串流(**ByteArrayInputStreamByteArrayOutputStream**)

【程式說明】

底下的範例為一個簡單的字串轉換程式,由使用者讀入一組小寫字串,再將字串轉換成位元陣列的型式,利用ByteArrayInputStream串流來讀取陣列中的位元資料,並將小寫字元轉換成大寫後輸出。

【範例程式】

//StringTransform.java 位元陣列串流範例

【執行結果】

2

圖 48 位元陣列串流範例程式執行結果

  • 具緩衝的字元串流(BufferReader、BufferWriter)

【程式說明】

本範例為一個簡單的檔案閱讀程式,利用具有緩衝區(Buffer)的BufferReader與BufferWriter,可以提高資料讀寫時的效率,並且在結合FileReader讀取使者指定的純文字檔案,將資料以readLine()的方式一次一行的輸出於螢幕上。

【範例程式】

//FileViewer.java 檔案內容瀏覽器範例

【執行結果】

2

圖 49 具緩衝字元輸入串流範例執行結果