JAVA IO 設計模式完全分析

一。引子(歸納地介紹Java的IO)java

  不管是哪一種編程語言,輸入跟輸出都是重要的一部分,Java也不例外,並且Java將輸入/輸出的功能和使用範疇作了很大的擴充。它採用了流的 機制來實現輸入/輸出,所謂流,就是數據的有序排列,而流能夠是從某個源(稱爲流源或Source of Stream)出來,到某個目的地(稱爲流匯或Sink of Stream)去的。由流的方向,能夠分紅輸入流和輸出流,一個程序從輸入流讀取數據向輸出流寫數據。程序員

  如,一個程序能夠用FileInputStream類從一個磁盤文件讀取數據,以下圖所示:編程

  像FileInputStream這樣的處理器叫作流處理器,它就像流的管道同樣,從一個流源吸入某種類型的數據,並輸出某種類型的數據。上面這種示意圖叫作流的管道圖。設計模式

  一樣道理,也能夠用FileOutputStream類向一個磁盤文件寫數據,以下圖所示:數組

從Decorator,Adapter模式看Java/IO庫(一)

  在實際應用這種機制並不沒有太大的用處,程序須要寫出地一般是很是結構化的信息,所以這些byte類型的數據其實是一些數值,文字,源代碼 等。Java的I/O庫提供了一個稱作連接(Chaining)的機制,能夠將一個流處理器跟另外一個流處理器首尾相接,以其中之一的輸出爲輸入,造成一個 流管道的連接。緩存

  例如,DataInputStream流處理器能夠把FileInputStream流對象的輸出看成輸入,將Byte類型的數據轉換成Java的原始類型和String類型的數據。以下圖所示:app

從Decorator,Adapter模式看Java/IO庫(一)

 

  相似地,向一個文件寫入Byte類型的數據不是一個簡單的過程。一個程序須要向一個文件裏寫入的數據每每都是結構化的,而Byte類型則是原始 類型。所以在寫的時候必須通過轉換。DataOutputStream流處理器提供了接收了原始數據類型和String數據類型,而這個流處理器的輸出數 據則是Byte類型。也就是說DataOutputStream能夠將源數據轉換成Byte類型的數據,再輸出來。編程語言

  這樣一來,就能夠將DataOutputStream與FileOutputStream連接起來,這樣程序就能夠將原始數據類型和String類型的源數據寫入這個連接好的雙重管道里面,達到將結構化數據寫到磁盤文件裏面的目的,以下圖所示:函數

從Decorator,Adapter模式看Java/IO庫(一)

  這又是連接的所發揮的大做用。測試

  流處理器所處理的流一定都有流源,而若是將流類所處理的流源分類的話,基本能夠分紅兩大類:

  第一 數組,String,File等,這一種叫原始流源。

  第二 一樣類型的流用作連接流類的流源,叫連接流源。

  二 Java I/O庫的設計原則

  Java語言的I/O庫是對各類常見的流源,流匯以及處理過程的抽象化。客戶端的Java程序沒必要知道最終的流源,流匯是磁盤上的文件仍是數組等;也沒必要關心數據是否通過緩衝的,能否按照行號讀取等處理的細節。

  書中提到了,對於第一次見到Java/IO庫的人,無不由於這個庫的龐雜而感到困惑;而對於熟悉這個庫的人,而又經常爲這個庫的設計是否得當而爭論不體。書的做者提出本身的意見,要理解Java I/O這個龐大而複雜的庫,關鍵是要掌握兩個對稱性跟兩個設計模式模式。

  Java I/O庫具備兩個對稱性,它們分別是:

  1 輸入-輸出對稱性,好比InputStream和OutputStream各自佔據Byte流的輸入與輸出的兩個平行的等級結構的根部。而Reader和Writer各自佔據Char流的輸入與輸出的兩個平行的等級結構的根部。

  2 byte-char對稱,InputStream和Reader的子類分別負責Byte和Char流的輸入;OutputStream和Writer的子類分別負責Byte和Char流的輸出,它們分別造成平行的等級結構。

  Java I/O庫的兩個設計模式:

  Java的I/O庫整體設計是符合裝飾者模式(Decorator)跟適配器模式(Adapter)的。如前所述,這個庫中處理流的類叫作流 類。引子裏所談到的FileInputStream,FileOutputStream,DataInputStream及 DataOutputStream都是流處理器的例子。

  1 裝飾者模式:在由InputStream,OutputStream,Reader和Writer表明的等級結構內部,有一些流處理器能夠 對另外一些流處理器起到裝飾做用,造成新的,具備改善了的功能的流處理器。裝飾者模式是Java I/O庫的總體設計模式。這樣的一個原則是符合裝飾者模式的,以下圖所示:

從Decorator,Adapter模式看Java/IO庫(一)

 

 

 

2 適配器模式:在由InputStream,OutputStream,Reader和Writer表明的等級結構內部,有一些流處理器是對其它類型的流源的適配。這就是適配器模式的應用,以下圖所示。

從Decorator,Adapter模式看Java/IO庫(一)

 

  適配器模式應用到了原始流處理器的設計上面,構成了I/O庫全部流處理器的起點。

  JDK爲程序員提供了大量的類庫,而爲了保持類庫的可重用性,可擴展性和靈活性,其中使用到了大量的設計模式,本文將介紹JDK的I/O包中使用到的Decorator模式,並運用此模式,實現一個新的輸出流類。

適配器類圖以下:

 

從Decorator,Adapter模式看Java/IO庫(三)

 

 

在看源代碼

Public class FileInputStream extends InputStream{
/* File Descriptor - handle to the open file */
  private FileDescriptor fd;
  public FileInputStream(FileDescriptor fdObj) {
   SecurityManager security = System.getSecurityManager();
   if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkRead(fdObj);
}
fd = fdObj;
}
public FileInputStream(File file) throws FileNotFoundException {
String name = file.getPath();
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
fd = new FileDescriptor();
open(name);
}
//其它代碼
} 

 

個人理解:

它繼承了InputStrem類型,同時持有一個對FileDiscriptor的引用,是將一個FileDiscriptor對象適配成InputStrem類型的對象形式的適配器模式。

 

Decorator模式簡介

  Decorator模式又名包裝器(Wrapper),它的主要用途在於給一個對象動態的添加一些額外的職責。與生成子類相比,它更具備靈活性。

  有時候,咱們須要爲一個對象而不是整個類添加一些新的功能,好比,給一個文本區添加一個滾動條的功能。咱們可使用繼承機制來實現這一功能,但 是這種方法不夠靈活,咱們沒法控制文本區加滾動條的方式和時機。並且當文本區須要添加更多的功能時,好比邊框等,須要建立新的類,而當須要組合使用這些功 能時無疑將會引發類的爆炸。

  咱們可使用一種更爲靈活的方法,就是把文本區嵌入到滾動條中。而這個滾動條的類就至關於對文本區的一個裝飾。這個裝飾(滾動條)必須與被裝飾 的組件(文本區)繼承自同一個接口,這樣,用戶就沒必要關心裝飾的實現,由於這對他們來講是透明的。裝飾會將用戶的請求轉發給相應的組件(即調用相關的方 法),並可能在轉發的先後作一些額外的動做(如添加滾動條)。經過這種方法,咱們能夠根據組合對文本區嵌套不一樣的裝飾,從而添加任意多的功能。這種動態的 對對象添加功能的方法不會引發類的爆炸,也具備了更多的靈活性。

  以上的方法就是Decorator模式,它經過給對象添加裝飾來動態的添加新的功能。以下是Decorator模式的UML圖:

從Decorator,Adapter模式看Java/IO庫(二)

從Decorator,Adapter模式看Java/IO庫(二)

 

Component爲組件和裝飾的公共父類,它定義了子類必須實現的方法。

  ConcreteComponent是一個具體的組件類,能夠經過給它添加裝飾來增長新的功能。

  Decorator是全部裝飾的公共父類,它定義了全部裝飾必須實現的方法,同時,它還保存了一個對於Component的引用,以便將用戶的請求轉發給Component,並可能在轉發請求先後執行一些附加的動做。

  ConcreteDecoratorA和ConcreteDecoratorB是具體的裝飾,可使用它們來裝飾具體的Component.

 

個人理解:

     上圖中的抽象連接流是一個Decorator,它不真正的裝飾,它只提供接口,讓它的子類去裝飾。至關於上圖中的具體連接流去裝飾原始流。

 

  JAVA IO包中的Decorator模式

  JDK提供的java.io包中使用了Decorator模式來實現對各類輸入輸出流的封裝。如下將以java.io.OutputStream及其子類爲例,討論一下Decorator模式在IO中的使用。

  首先來看一段用來建立IO流的代碼:

  如下是代碼片斷:

  try {

  OutputStream out = new DataOutputStream(new FileOutputStream("test.txt"));

  } catch (FileNotFoundException e) {

  e.printStackTrace();

  }

  這段代碼對於使用過JAVA輸入輸出流的人來講再熟悉不過了,咱們使用DataOutputStream封裝了一個 FileOutputStream.這是一個典型的Decorator模式的使用,FileOutputStream至關於 Component,DataOutputStream就是一個Decorator.將代碼改爲以下,將會更容易理解:

  如下是代碼片斷:

  try {

  OutputStream out = new FileOutputStream("test.txt");

  out = new DataOutputStream(out);

  } catch(FileNotFoundException e) {

  e.printStatckTrace();

  }

  因爲FileOutputStream和DataOutputStream有公共的父類OutputStream,所以對對象的裝飾對於用戶來講幾乎是透明的。下面就來看看OutputStream及其子類是如何構成Decorator模式的:

  OutputStream是一個抽象類,它是全部輸出流的公共父類,其源代碼以下:

  如下是代碼片斷:

  public abstract class OutputStream implements Closeable, Flushable {

  public abstract void write(int b) throws IOException;

  ……

  }

  它定義了write(int b)的抽象方法。這至關於Decorator模式中的Component類。

  ByteArrayOutputStream,FileOutputStream 和 PipedOutputStream 三個類都直接從OutputStream繼承,以ByteArrayOutputStream爲例:

  如下是代碼片斷:

  public class ByteArrayOutputStream extends OutputStream {

  protected byte buf[];

  protected int count;

  public ByteArrayOutputStream() {

  this(32);

  }

  public ByteArrayOutputStream(int size) {

  if (size 〈 0) {

  throw new IllegalArgumentException("Negative initial size: "

  + size);

  }

  buf = new byte[size];

  }

  public synchronized void write(int b) {

  int newcount = count + 1;

  if (newcount 〉 buf.length) {

  byte newbuf[] = new byte[Math.max(buf.length 〈〈 1, newcount)];

  System.arraycopy(buf, 0, newbuf, 0, count);

  buf = newbuf;

  }

  buf[count] = (byte)b;

  count = newcount;

  }

  ……

  }

  它實現了OutputStream中的write(int b)方法,所以咱們能夠用來建立輸出流的對象,並完成特定格式的輸出。它至關於Decorator模式中的ConcreteComponent類。

  接着來看一下FilterOutputStream,代碼以下:

  如下是代碼片斷:

  public class FilterOutputStream extends OutputStream {

  protected OutputStream out;

  public FilterOutputStream(OutputStream out) {

  this.out = out;

  }

  public void write(int b) throws IOException {

  out.write(b);

  }

  ……

  }

  一樣,它也是從OutputStream繼承。可是,它的構造函數很特別,須要傳遞一個OutputStream的引用給它,而且它將保存對此 對象的引用。而若是沒有具體的OutputStream對象存在,咱們將沒法建立FilterOutputStream.因爲out既能夠是指向 FilterOutputStream類型的引用,也能夠是指向ByteArrayOutputStream等具體輸出流類的引用,所以使用多層嵌套的方 式,咱們能夠爲ByteArrayOutputStream添加多種裝飾。這個FilterOutputStream類至關於Decorator模式中的 Decorator類,它的write(int b)方法只是簡單的調用了傳入的流的write(int b)方法,而沒有作更多的處理,所以它本質上沒有對流進行裝飾,因此繼承它的子類必須覆蓋此方法,以達到裝飾的目的。

  BufferedOutputStream 和 DataOutputStream是FilterOutputStream的兩個子類,它們至關於Decorator模式中的 ConcreteDecorator,並對傳入的輸出流作了不一樣的裝飾。以BufferedOutputStream類爲例:

  如下是代碼片斷:

  public class BufferedOutputStream extends FilterOutputStream {

  ……

  private void flushBuffer() throws IOException {

  if (count 〉 0) {

  out.write(buf, 0, count);

  count = 0;

  }

  }

  public synchronized void write(int b) throws IOException {

  if (count 〉= buf.length) {

  flushBuffer();

  }

  buf[count++] = (byte)b;

  }

  ……

  }

  這個類提供了一個緩存機制,等到緩存的容量達到必定的字節數時才寫入輸出流。首先它繼承了FilterOutputStream,而且覆蓋了父 類的write(int b)方法,在調用輸出流寫出數據前都會檢查緩存是否已滿,若是未滿,則不寫。這樣就實現了對輸出流對象動態的添加新功能的目的。

  下面,將使用Decorator模式,爲IO寫一個新的輸出流。

  本身寫一個新的輸出流

  瞭解了OutputStream及其子類的結構原理後,咱們能夠寫一個新的輸出流,來添加新的功能。這部分中將給出一個新的輸出流的例子,它將 過濾待輸出語句中的空格符號。好比須要輸出"java io OutputStream",則過濾後的輸出爲"javaioOutputStream".如下爲SkipSpaceOutputStream類的代碼:

  如下是代碼片斷:

  import java.io.FilterOutputStream;

  import java.io.IOException;

  import java.io.OutputStream;

  /**

  * A new output stream, which will check the space character

  * and won‘t write it to the output stream.

  * @author Magic

  *

  */

  public class SkipSpaceOutputStream extends FilterOutputStream {

  public SkipSpaceOutputStream(OutputStream out) {

  super(out);

  }

  /**

  * Rewrite the method in the parent class, and

  * skip the space character.

  */

  public void write(int b) throws IOException{

  if(b!=‘ ’){

  super.write(b);

  }

  }

  }

  它從FilterOutputStream繼承,而且重寫了它的write(int b)方法。在write(int b)方法中首先對輸入字符進行了檢查,若是不是空格,則輸出。

  如下是一個 測試程序:

  如下是代碼片斷:

  import java.io.BufferedInputStream;

  import java.io.DataInputStream;

  import java.io.DataOutputStream;

  import java.io.IOException;

  import java.io.InputStream;

  import java.io.OutputStream;

  /**

  * Test the SkipSpaceOutputStream.

  * @author Magic

  *

  */

  public class Test {

  public static void main(String[] args){

  byte[] buffer = new byte[1024];

  /**

  * Create input stream from the standard input.

  */

  InputStream in = new BufferedInputStream(new DataInputStream(System.in));

  /**

  * write to the standard output.

  */

  OutputStream out = new SkipSpaceOutputStream(new DataOutputStream(System.out));

  try {

  System.out.println("Please input your words: ");

  int n = in.read(buffer,0,buffer.length);

  for(int i=0;i〈n;i++){

  out.write(buffer[i]);

  }

  } catch (IOException e) {

  e.printStackTrace();

  }

  }

  }

  執行以上測試程序,將要求用戶在console窗口中輸入信息,程序將過濾掉信息中的空格,並將最後的結果輸出到console窗口。好比:

  如下是引用片斷:

  Please input your words:

  a b c d e f

  abcdef

  總   結

  在java.io包中,不只OutputStream用到了Decorator設計模式,InputStream,Reader,Writer 等都用到了此模式。而做爲一個靈活的,可擴展的類庫,JDK中使用了大量的設計模式,好比在Swing包中的MVC模式,RMI中的Proxy模式等等。 對於JDK中模式的研究不只能加深對於模式的理解,並且還有利於更透徹的瞭解類庫的結構和組成。

相關文章
相關標籤/搜索