Java IO詳細介紹

Java IO
江蘇 無錫 繆小東
       本篇主要講述IO相關的內容,主要包括:與IO相關的簡單的歷史背景知識;Java IO的簡單分類;與IO設計相關的兩個模式;同時列舉幾個簡單的例子;分析其中幾個實現的源代碼;最後給大家一些簡單擴展的例子。治學先治史,下面我們先從簡單的歷史開始吧!
 
一、      歷史背景
       “對語言設計人員來說,創建好的輸入/輸出系統是一項特別困難的任務。
――Think in Java
       無論是系統、還是語言的設計中IO的設計都是異常複雜的。面臨的最大的挑戰一般是如何覆蓋所有可能的因素,我們不僅僅要考慮文件、控制檯、網絡、內存等不同的種類,而且要處理大量的不同的讀取方式,如:順序讀取、隨機讀取,二進制讀取、字符讀取,按行讀取、按字符讀取……
       Linux是第一個將設備抽象爲文件的操作系統,在Linux中所有的外部設備都可以用讀取文件的方法讀取,這樣編程人員就可以以操作文件的方法操作任何設備。C++在IO方面也做了一些改進――引進了流的概念,我們可以通過cin、cout讀寫一些對象。Java語言在IO設計方面取得較大的成功,它是完全面向對象的,主要採用裝飾器模式避免大量的類,包括了最大的可能性,提供了較好的擴展機制……
       “Java庫的設計者通過創建大量類來攻克這個難題。事實上,Java的IO系統採用瞭如此多的類,以致剛開始會產生不知從何處入手的感覺(具有諷刺意味的是,Java的IO設計初衷實際要求避免過多的類)。” 上面一段來自《Think in Java》,確實很多初學者剛剛學習java的IO時會比較茫然,不過等我們知道裝飾器模式(Decorator)的用意、場景及其在Java的IO包中的使用,你可能會真正領會整個IO的FrameWork。
二、IO的分類
    Java IO一般包含兩個部分:1.java.io包中堵塞型IO;2.java.nio包中的非堵塞型IO,通常稱爲New IO。學過操作系統的朋友都知道系統運行的瓶頸一般在於IO操作,一般打開某個IO通道需要大量的時間,同時端口中不一定就有足夠的數據,這樣read方法就一直等待讀取此端口的內容,從而浪費大量的系統資源。有人也許會提出使用java的多線程技術啊!但是在當前進程中創建線程也是要花費一定的時間和系統資源的,因此不一定可取。Java New IO的非堵塞技術主要採用了Observer模式,就是有一個具體的觀察者和=監測IO端口,如果有數據進入就會立即通知相應的應用程序。這樣我們就避免建立多個線程,同時也避免了read等待的時間。不過本篇主要講述java的堵塞型IO,就是我們通常應用的那個包。
    打開你的java.io包你可以看到Java的IO包含大量的類和接口(JDK1.6中包含83個類或者接口),如此衆多的類和接口似乎無從下手。下面就將IO簡單地分類。Java的IO主要包含三個部分:1.流式部分――IO的主體部分;2.非流式部分――主要包含一些輔助流式部分的類,如:File類、RandomAccessFile類和FileDescriptor等類;3.文件讀取部分的與安全相關的類,如:SerializablePermission類。以及與本地操作系統相關的文件系統的類,如:FileSystem類和Win32FileSystem類和WinNTFileSystem類。
       流式部分可以概括爲:兩個對應一個橋樑。兩個對應指:1.字節流(Byte Stream)和字符流(Char Stream)的對應;2.輸入和輸出的對應。一個橋樑指:從字節流到字符流的橋樑。對應於輸入和輸出爲InputStreamReader和OutputStreamWriter。
       在流的具體類中又可以具體分爲:1.介質流(Media Stream或者稱爲原始流Raw Stream)――主要指一些基本的流,他們主要是從具體的介質上,如:文件、內存緩衝區(Byte數組、Char數組、StringBuffer對象)等,讀取數據;2.過濾流(Filter Stream)――主要指所有FilterInputStream/FilterOutputStream和FilterReader/FilterWriter的子類,主要是對其包裝的類進行某些特定的處理,如:緩存等。
 
三、IO中的流
       流具有最基本的特點:“One dimension , one direction .” 即流是一維的,同時流是單向的。關於維和我們通常說的一維長度,二維平面,三維空間,四維時空……是同一個概念,流就是一維的。單向就是只可以一個方向(按順序從頭至尾依次)讀取,不可以讀到某個位置,再返回前面某個位置。流的概念和實際水流的概念基本一致,水只可以從高向低一個方向流動。我們某時在目地喝了一口水,下次在同一個地點喝水已經不是當時的那片水了。
       流的這種特性在JMS(Java Message Service)的API設計中得到了體現。JMS是J2EE平臺下面向消息中間件的一個標準。(關於中間件技術有機會和大家探討)JMS中有五種具體類型的消息,這些消息一般分爲兩類:1.流式的消息――包含ByteMessage和StreamMessage;2.非流式的消息――包含TextMessage、ObjectMessage和MapMessage。我們在明白IO中流的特點後,基本可以明白JMS API設計者的意圖。
       可能有些場合我們需要在文件中隨機插入數據、在流中來來回回地執行某些操作,這時候我們絕對不可以使用流相關的對象。很幸運JDK的設計者爲我們設計了一個單獨的類RandomAccessFile,它可以完成打開、關閉文件、以基本數據類型的方式讀取數據、讀取下一個行、以UTF等格式讀取數據、寫入各種類型的數據、比較特殊的是他可以通過文件指針的seek方法讓文件指針移到某個位置,可以通過getFilePointer方法得到當前指針的位置、可以通過length()方法得到當前文件的容量、通過getFD得到FileDescriptor對象,通過getChannel方法得到FileChannel對象,從而和New IO整合。()()()
       下面比較簡單的分析IO中的各個對象吧!
3.1 IO中的輸入字節流
       下面是IO中輸入字節流的繼承圖。
o        InputStream
o        ByteArrayInputStream
o        FileInputStream
o        FilterInputStream
o        BufferedInputStream
o        DataInputStream
o        LineNumberInputStream
o        PushbackInputStream
o        ObjectInputStream
o        PipedInputStream
o        SequenceInputStream
o        StringBufferInputStream
在上面的關係圖中可以看出:
1.InputStream是所有的輸入字節流的父類,它是一個抽象類。
2. ByteArrayInputStream、StringBufferInputStream、FileInputStream是三種基本的介質流,它們分別將Byte數組、StringBuffer、和本地文件中讀取數據。PipedInputStream是從與其它線程共用的管道中讀取數據,與Piped相關的知識會用專門的一小節講解。
3. ObjectInputStream和所有FilterInputStream的子類都是裝飾流(裝飾器模式的主角)。下表列出了這些流的功能及如何使用它們(具體使用在講解完裝飾器模式後會舉幾個例子)。
基本輸入字節流:
功能
如何構造
怎樣使用
ByteArrayInputStream
將內存中的Byte數組適配爲一個InputStream。
從內存中的Byte數組創建該對象(2種方法)
一般作爲數據源,會使用其它裝飾流提供額外的功能,一般都建議加個緩衝功能。
StringBufferInputStream
將內存中的字符串適配爲一個InputStream。
從一個String對象創建該對象。底層的實現使用StringBuffer。該類被Deprecated。主要原因是StringBuffer不應該屬於字節流,所以推薦使用StringReader。
一般作爲數據源,同樣會使用其它裝飾器提供額外的功能。
FileInputStream
最基本的文件輸入流。主要用於從文件中讀取信息。
通過一個代表文件路徑的 String、File對象或者 FileDescriptor對象創建。
一般作爲數據源,同樣會使用其它裝飾器提供額外的功能。
PipedInputStream
讀取從對應PipedOutputStream寫入的數據。在流中實現了管道的概念。
利用對應的PipedOutputStream創建。
在多線程程序中作爲數據源,同樣會使用其它裝飾器提供額外的功能。
SequenceInputStream
將2個或者多個InputStream 對象轉變爲一個InputStream.
使用兩個InputStream 或者內部對象爲InputStream 的Enumeration對象創建該對象。
一般作爲數據源,同樣會使用其它裝飾器提供額外的功能。
FilterInputStream
給其它被裝飾對象提供額外功能的抽象類
主要子類見下表
 
裝飾、輸入字節流:
功能
如何構造
怎樣使用
DataInputStream
一般和DataOutputStream配對使用,完成基本數據類型的讀寫。
利用一個InputStream構造。
提供了大量的讀取基本數據類新的讀取方法。
BufferedInputStream
使用該對象阻止每次讀取一個字節都會頻繁操作IO。將字節讀取一個緩存區,從緩存區讀取。
利用一個InputStream、或者帶上一個自定義的緩存區的大小構造。
使用InputStream的方法讀取,只是背後多一個緩存的功能。設計模式中透明裝飾器的應用。
LineNumberInputStream
跟蹤輸入流中的行號。可以調用getLineNumber( )和 setLineNumber(int)方法得到和設置行號。
利用一個InputStream構造。
緊緊增加一個行號。可以象使用其它InputStream一樣使用。
PushbackInputStream
可以在讀取最後一個byte 後將其放回到緩存中。
利用一個InputStream構造。
一般僅僅會在設計compiler的scanner 時會用到這個類。在我們的java語言的編譯器中使用它。很多程序員可能一輩子都不需要。
3.2 IO中的輸出字節流
       下面是IO中輸出字節流的繼承圖。
    • OutputStream
      • ByteArrayOutputStream
      • FileOutputStream
      • FilterOutputStream
        • BufferedOutputStream
        • DataOutputStream
        • PrintStream
      • ObjectOutputStream
      • PipedOutputStream
在上面的關係圖中可以看出:1.OutputStream是所有的輸出字節流的父類,它是一個抽象類。2. ByteArrayOutputStream、FileOutputStream是兩種基本的介質流,它們分別向Byte數組、和本地文件中寫入數據。PipedOutputStream是向與其它線程共用的管道中寫入數據, 3. ObjectOutputStream和所有FilterOutputStream的子類都是裝飾流。下表列出了輸出字節流的功能及如何使用它們。
功能
如何構造
怎樣使用
ByteArrayOutputStream
在內存中創建一個buffer。所有寫入此流中的數據都被放入到此buffer中。
無參或者使用一個可選的初始化buffer的大小的參數構造。
一般將其和FilterOutputStream套接得到額外的功能。建議首先和BufferedOutputStream套接實現緩衝功能。通過toByteArray方法可以得到流中的數據。(不通明裝飾器的用法)
FileOutputStream
將信息寫入文件中。
使用代表文件路徑的String、File對象或者 FileDescriptor對象創建。還可以加一個代表寫入的方式是否爲append的標記。
一般將其和FilterOutputStream套接得到額外的功能。
PipedOutputStream
任何寫入此對象的信息都被放入對應PipedInputStream 對象的緩存中,從而完成線程的通信,實現了“管道”的概念。具體在後面詳細講解。
利用PipedInputStream構造
在多線程程序中數據的目的地的。一般將其和FilterOutputStream套接得到額外的功能。
FilterOutputStream
實現裝飾器功能的抽象類。爲其它OutputStream對象增加額外的功能。
見下表
見下表
       裝飾輸出字節流:
功能
如何構造
怎樣使用
DataOutputStream
通常和DataInputStream配合使用,使用它可以寫入基本數據類新。
使用OutputStream構造
包含大量的寫入基本數據類型的方法。
PrintStream
產生具有格式的輸出信息。(一般地在java程序中DataOutputStream用於數據的存儲,即J2EE中持久層完成的功能,PrintStream完成顯示的功能,類似於J2EE中表現層的功能)
使用OutputStream和一個可選的表示緩存是否在每次換行時是否flush的標記構造。還提供很多和文件相關的構造方法。
一般是一個終極(“final”)的包裝器,很多時候我們都使用它!
BufferedOutputStream
使用它可以避免頻繁地向IO寫入數據,數據一般都寫入一個緩存區,在調用flush方法後會清空緩存、一次完成數據的寫入。
從一個OutputStream或者和一個代表緩存區大小的可選參數構造。
提供和其它OutputStream一致的接口,只是內部提供一個緩存的功能。
 
3.3字節流的輸入與輸出的對應
       在3.1節講過輸入與輸出的對應,下圖表示字節流部分的輸入與輸出的對應關係。 
 
 
       上圖中藍色的爲主要的對應部分,紅色的部分就是不對應部分。我習慣上稱之爲“不入流”部分。紫色的虛線部分代表這些流一般要搭配使用。從上面的圖中可以看出Java IO中的字節流是極其對稱的。
“存在及合理”我們看看這些字節流中不太對稱的幾個類吧!
1.         LineNumberInputStream主要完成從流中讀取數據時,會得到相應的行號,至於什麼時候分行、在哪裏分行是由改類主動確定的,並不是在原始中有這樣一個行號。在輸出部分沒有對應的部分,我們完全可以自己建立一個LineNumberOutputStream,在最初寫入時會有一個基準的行號,以後每次遇到換行時會在下一行添加一個行號,看起來也是可以的。好像更不入流了。
2.         PushbackInputStream的功能是查看最後一個字節,不滿意就放入緩衝區。主要用在編譯器的語法、詞法分析部分。輸出部分的BufferedOutputStream幾乎實現相近的功能。
3.         StringBufferInputStream已經被Deprecated,本身就不應該出現在InputStream部分,主要因爲String應該屬於字符流的範圍。已經被廢棄了,當然輸出部分也沒有必要需要它了!還允許它存在只是爲了保持版本的向下兼容而已。
4.         SequenceInputStream可以認爲是一個工具類,將兩個或者多個輸入流當成一個輸入流依次讀取。完全可以從IO包中去除,還完全不影響IO包的結構,卻讓其更“純潔”――純潔的Decorator模式。
5.         PrintStream也可以認爲是一個輔助工具。主要可以向其他輸出流,或者FileInputStream寫入數據,本身內部實現還是帶緩衝的。本質上是對其它流的綜合運用的一個工具而已。一樣可以踢出IO包!System.out和System.out就是PrintStream的實例!
藍色的部分是IO字節流的主要組成部分,存在極強的對稱關係。關於搭配使用的三對類補充一下:ObjectInputStream/ObjectOutputStream和DataInputStream/DataOutputStream主要是要求寫對象/數據和讀對象/數據的次序要保持一致,否則輕則不能得到正確的數據,重則拋出異常(一般會如此);PipedInputStream/PipedOutputStream在創建時一般就一起創建,調用它們的讀寫方法時會檢查對方是否存在,或者關閉!道理極其簡單――對方都不在了,怎麼交互啊!

 

3.4 字節流與字符流

       從上面我們可以看出IO中的字節流是極其複雜的,存在大量的類,到目前爲止還沒有真正使用它們,使用它們應該也是極其複雜的吧!JDK1.1SunIO庫進行了重大的改進。看到ReaderWriter類時,大多數人的第一個感覺(不要太相信感覺哦!感覺也許會欺騙你的!)就是它們是用來替換原來的InputStreamOutputStream類。有新的類,幹嗎還使用舊的呢!?但實情並非如此。儘管Sun不建議使用原始的流庫中的某些功能,但原來的流依然得到了保留,不僅爲了保持向後兼容,主要原因是新庫不是舊庫的替代,而是對舊庫的增強。從以下兩點可以明顯地看出:

(1) 在老式的類層次結構里加入了新的類,這表明 Sun公司沒有放棄老式流庫的意圖。

(2) 在許多情況下,新庫中類的使用需要聯合老結構中的類。爲達到這個目的,需要使用一些“橋”類,如:InputStreamReader將一個InputStream轉換成ReaderOutputStreamWriter將一個OutputStream轉換成Writer

那麼Sun爲什麼在Java 1.1裏添加了ReaderWriter層次,最重要的原因便是國際化(Internationalization――i18n)的需求。老式IO流層次結構只支持8位字節流,不能很好地控制16位的Unicode字符。Java本身支持UnicodeSun又一致吹噓其支持Unicode,因此有必要實現一個支持Unicode的流的層次結構,所以出現了ReaderWriter層次,以提供對所有IO操作中的Unicode的支持。除此之外,新庫也對速度進行了優化,可比舊庫更快地運行。

8位的字節流和16位的字符流的對應關係,可以從ByteInputStream/ByteOutputStreamCharArrayInputStream/CharArrayOutputStream的對應關係中看出端倪。(還沒看出來啊!趕緊去看看Java的基本數據類型)。

因此在JavaIO體系中存在字節流和字符流的對應關係。下面就看看字符流吧!

3.5 IO中的輸入字符流

       下面是IO中輸入字符流的繼承圖。

    • Reader

      • BufferedReader

        • LineNumberReader

      • CharArrayReader

      • FilterReader

        • PushbackReader

      • InputStreamReader

        • FileReader

      • PipedReader

      • StringReader

在上面的關係圖中可以看出:1.Reader是所有的輸入字符流的父類,它是一個抽象類。2.CharReaderStringReader是兩種基本的介質流,它們分別將Char數組、String中讀取數據。PipedReader是從與其它線程共用的管道中讀取數據。3. BufferedReader很明顯就是一個裝飾器,它和其子類負責裝飾其它Reader對象。4.FilterReader是所有自定義具體裝飾流的父類,其子類PushbackReaderReader對象進行裝飾,會增加一個行號。5.InputStreamReader是一個連接字節流和字符流的橋樑,它將字節流轉變爲字符流。FileReader可以說是一個達到此功能、常用的工具類,在其源代碼中明顯使用了將FileInputStream轉變爲Reader的方法。我們可以從這個類中得到一定的技巧。

       Reader中各個類的用途和使用方法基本和InputStream中的類使用一致。後面會有ReaderInputStream的對應關係。

3.6 IO中的輸出字符流

下面是IO中輸出字符流的繼承圖。

    • Writer

      • BufferedWriter

      • CharArrayWriter

      • FilterWriter

      • OutputStreamWriter

        • FileWriter

      • PipedWriter

      • PrintWriter

      • StringWriter

在上面的關係圖中可以看出:1.Writer是所有的輸出字符流的父類,它是一個抽象類。2. CharArrayWriterStringWriter是兩種基本的介質流,它們分別向Char數組、String中寫入數據。PipedWriter是向與其它線程共用的管道中寫入數據, 3. BufferedWriter是一個裝飾器爲Writer提供緩衝功能。4.PrintWriterPrintStream極其類似,功能和使用也非常相似。5.OutputStreamWriterOutputStreamWriter轉換的橋樑,它的子類FileWriter其實就是一個實現此功能的具體類(具體可以研究一下Source Code)。功能和使用和OutputStream極其類似,後面會有它們的對應圖。

3.7字符流的輸入與輸出的對應

下圖爲字符流的輸入與輸出的對應關係圖:

 

 

       對應關係和字節流的輸入輸出基本一致,不必多說了吧!在下面的源代碼閱讀部分會仔細研究一些!

3.8字節流和字符流的對應

       JavaIO中存在輸入、輸出的對應和字節流和字符流的對應,下面就看看字節流和字符流的對應吧!

3.8.1輸入的對應

       下圖是IO中字節輸入流與字符輸入流的對應圖:

 

 

       藍色的表示對應的部分,紅色的表示不對應的部分。至於爲什麼不對應還是你自己多看看源代碼、多考慮考慮吧!還要強調一點就是即使對應,它們的繼承關係也是不太對應的。

 

3.8.2輸出的對應

下圖是IO中字節輸出流與字符輸出流的對應圖:

 

 

       不多說了!等講述了AdapterDecorator模式會基本明白IO架構的!通過幾個實例一般就可以使用了!

InputStreamByteArrayInputStream

       江蘇 無錫 繆小東

本篇主要分析:1.如何將byte數組適配至ByteArrayInputStream,對應與IO部分的適配器模式;2.BufferedInputStream的工作原理,對應於IO的裝飾器模式,會首先研究InputStreamFilterInputStream的源代碼,同時會將要談談軟件設計中的緩存相關的知識。後面專門一章分析PipedInputStreamPipedOutStream,簡單談談管道相關的知識,以及軟件架構的想法。

1 InputStream

InputStream 是輸入字節流部分,裝飾器模式的頂層類。主要規定了輸入字節流的公共方法。

 

package java.io;

public abstract class InputStream implements Closeable {

    private static final int SKIP_BUFFER_SIZE = 2048;  //用於skip方法,和skipBuffer相關

    private static byte[] skipBuffer;    // skipBuffer is initialized in skip(long), if needed.

 

public abstract int read() throws IOException;  //從輸入流中讀取下一個字節,

                                                                                             //正常返回0-255,到達文件的末尾返回-1

                                                        //在流中還有數據,但是沒有讀到時該方法會阻塞(block

                                                        //Java IONew IO的區別就是阻塞流和非阻塞流

                                                        //抽象方法哦!不同的子類不同的實現哦!

 

         //將流中的數據讀入放在byte數組的第off個位置先後的len個位置中

         //放回值爲放入字節的個數。

    public int read(byte b[], int off, int len) throws IOException {           //

         if (b == null) {

             throw new NullPointerException();

         } else if (off < 0 || len < 0 || len > b.length - off) {

             throw new IndexOutOfBoundsException();

         } else if (len == 0) {

             return 0;

         }        //檢查輸入是否正常。一般情況下,檢查輸入是方法設計的第一步

         int c = read();                                                              //讀取下一個字節

         if (c == -1) {    return -1;   }                           //到達文件的末端返回-1

         b[off] = (byte)c;                                                   //放回的字節downcast

         int i = 1;                                                                        //已經讀取了一個字節

         try {

             for (; i < len ; i++) {                          //最多讀取len個字節,所以要循環len

                   c = read();                                       //每次循環從流中讀取一個字節

                                                                                    //由於read方法阻塞,

//所以read(byte[],int,int)也會阻塞

                   if (c == -1) {            break;           }       //到達末尾,理所當然放回-1

                   b[off + i] = (byte)c;                                    //讀到就放入byte數組中

             }

         } catch (IOException ee) {     }

         return i;

         //上面這個部分其實還有一點比較重要,int i = 1;在循環的外圍,或許你經常見到,

         //或許你只會在循環是才聲明,爲什麼呢?

         //聲明在外面,增大了變量的生存週期(在循環外面),所以後面可以return返回

         //極其一般的想法。在類成員變量生命週期中使用同樣的理念。

         //在軟件設計中,類和類的關係中也是一樣的。

    }        //這個方法在利用抽象方法read,某種意義上簡單的Templete模式。

 

    public int read(byte b[]) throws IOException {

                   return read(b, 0, b.length);

    }                           //利用上面的方法read(byte[] b)

 

    public long skip(long n) throws IOException {

         long remaining = n;                                  //方法內部使用的、表示要跳過的字節數目,

//使用它完成一系列字節讀取的循環

         int nr;

         if (skipBuffer == null)

             skipBuffer = new byte[SKIP_BUFFER_SIZE];                   //初始化一個跳轉的緩存

         byte[] localSkipBuffer = skipBuffer;                                      //本地化的跳轉緩存

         if (n <= 0) {    return 0;      }                           //檢查輸入參數,應該放在方法的開始

         while (remaining > 0) {                                      //一共要跳過n個,每次跳過部分,循環

             nr = read(localSkipBuffer, 0, (int) Math.min(SKIP_BUFFER_SIZE, remaining));

                                                        //利用上面的read(byte[],int,int)方法儘量讀取n個字節  

             if (nr < 0) {  break;    }                          //讀到流的末端,則返回

             remaining -= nr;                                       //沒有完全讀到需要的,則繼續循環

         }       

         return n - remaining;//返回時要麼全部讀完,要麼因爲到達文件末端,讀取了部分

    }

 

    public int available() throws IOException {                  //查詢流中還有多少可以讀取的字節

                   return 0;

    }

         //該方法不會block。在java中抽象類方法的實現一般有以下幾種方式:

//1.拋出異常(java.util);2.“實現。象上面這種。子類在必要的時候覆蓋它。

//3.“實現。下面有例子。

 

    public void close() throws IOException {}

         //關閉當前流、同時釋放與此流相關的資源

 

    public synchronized void mark(int readlimit) {}

         //在當前位置對流進行標記,必要的時候可以使用reset方法返回。

         //markSupport可以查詢當前流是否支持mark

 

    public synchronized void reset() throws IOException {

                   throw new IOException("mark/reset not supported");

    }

         //mark過的流進行復位。只有當流支持mark時纔可以使用此方法。

         //看看markavailablereset方法。體會爲什麼?!

 

    public boolean markSupported() {           //查詢是否支持mark

                   return false;

    }                 //絕大部分不支持,因此提供默認實現,返回false。子類有需要可以覆蓋。

        

}

 

2 FilterInputStream

       這是字節輸入流部分裝飾器模式的核心。是我們在裝飾器模式中的Decorator對象,主要完成對其它流裝飾的基本功能。下面是它的源代碼:

package java.io;

 

//該類對被裝飾的流進行基本的包裹。不增加額外的功能。

//客戶在需要的時候可以覆蓋相應的方法。具體覆蓋可以在ByteInputStream中看到!

public class FilterInputStream extends InputStream {

    protected volatile InputStream in;                       //將要被裝飾的字節輸入流

 

    protected FilterInputStream(InputStream in) {   //通過構造方法傳入此被裝飾的流

                   this.in = in;

    }

         //裝飾器的代碼特徵:被裝飾的對象一般是裝飾器的成員變量

         //上面幾行可以看出。

 

         //下面這些方法,完成最小的裝飾――0裝飾,只是調用被裝飾流的方法而已

 

    public int read() throws IOException {

                   return in.read();

    }

 

    public int read(byte b[]) throws IOException {

                   return read(b, 0, b.length);

    }

 

    public int read(byte b[], int off, int len) throws IOException {

                   return in.read(b, off, len);

    }

 

    public long skip(long n) throws IOException {

                   return in.skip(n);

    }

 

    public int available() throws IOException {

                   return in.available();

    }

 

    public void close() throws IOException {

                   in.close();

    }

 

    public synchronized void mark(int readlimit) {

                   in.mark(readlimit);

    }

 

    public synchronized void reset() throws IOException {

                   in.reset();

    }

 

    public boolean markSupported() {

                   return in.markSupported();

}

//以上的方法,都是通過調用被裝飾對象in完成的。沒有添加任何額外功能

//裝飾器模式中的Decorator對象,不增加被裝飾對象的功能。

//它是裝飾器模式中的核心。更多關於裝飾器模式的理論請閱讀博客中的文章。

}

 

       以上分析了所有字節輸入流的公共父類InputStream和裝飾器類FilterInputStream類。他們是裝飾器模式中兩個重要的類。更多細節請閱讀博客中裝飾器模式的文章。下面將講解一個具體的流ByteArrayInputStream,不過它是採用適配器設計模式。

 

3 ByteArrayByteArrayInputStream的適配

// ByteArrayInputStream內部有一個byte類型的buffer

//很典型的適配器模式的應用――將byte數組適配流的接口。

//下面是源代碼分析:

 

package java.io;

 

public class ByteArrayInputStream extends InputStream {

    protected byte buf[];                //內部的buffer,一般通過構造器輸入

protected int pos;                   //當前位置的cursor。從0byte數組的長度。

//byte[pos]就是read方法讀取的字節

    protected int mark = 0;           //mark的位置。

    protected int count;                          //流中字節的數目。不一定與byte[]的長度一致???

 

    public ByteArrayInputStream(byte buf[]) {//從一個byte[]創建一個ByteArrayInputStream

         this.buf = buf;                                                      //初始化流中的各個成員變量

        this.pos = 0;

         this.count = buf.length;                              //count就等於buf.length

    }

 

    public ByteArrayInputStream(byte buf[], int offset, int length) {                //構造器

         this.buf = buf;

        this.pos = offset;                                                                                      //與上面不同

         this.count = Math.min(offset + length, buf.length);

        this.mark = offset;                                                                                             //與上面不同

    }

 

    public synchronized int read() {                                           //從流中讀取下一個字節

                   return (pos < count) ? (buf[pos++] & 0xff) : -1; //返回下一個位置的字節

                                                                                                                //流中沒有數據則返回-1

    }

 

         //下面這個方法很有意思!從InputStream中可以看出其提供了該方法的實現。

         //爲什麼ByteArrayInputStream要覆蓋此方法呢?

         //同樣的我們在Java Collections Framework中可以看到:

//AbstractCollection利用iterator實現了Collecion接口的很多方法。但是,

//ArrayList中卻有很多被子類覆蓋了。爲什麼如此呢??

 

    public synchronized int read(byte b[], int off, int len) {

         if (b == null) {                                                               //首先檢查輸入參數的狀態是否正確

             throw new NullPointerException();

         } else if (off < 0 || len < 0 || len > b.length - off) {

             throw new IndexOutOfBoundsException();

         }

         if (pos >= count) {             return -1;             }

         if (pos + len > count) {      len = count - pos;         }

         if (len <= 0) {           return 0;     }

         System.arraycopy(buf, pos, b, off, len);                     //java中提供數據複製的方法

         pos += len;

         return len;

    }

         //出於速度的原因!他們都用到System.arraycopy方法。想想爲什麼?

         //某些時候,父類不能完全實現子類的功能,父類的實現一般比較通用。

//當子類有更有效的方法時,我們會覆蓋這些方法。這樣可是不太OO的哦!

 

         //下面這個方法,在InputStream中也已經實現了。

//但是當時是通過將字節讀入一個buffer中實現的,好像效率低了一點。

//看看下面這段代碼,是否極其簡單呢?!

    public synchronized long skip(long n) {

         if (pos + n > count) {    n = count - pos;       }        //當前位置,可以跳躍的字節數目

         if (n < 0) {       return 0;     }                                    //小於0,則不可以跳躍

         pos += n;                                                                              //跳躍後,當前位置變化

         return n;

    }                                    //InputStream中的方法簡單、高效吧!

 

    public synchronized int available() {

                   return count - pos;

    }

         //查詢流中還有多少字節沒有讀取。

//在我們的ByteArrayInputStream中就是當前位置以後字節的數目。  

 

    public boolean markSupported() {                   

                   return true;

    }        //ByteArrayInputStream支持mark所以返回true

 

    public void mark(int readAheadLimit) {            

                   mark = pos;

    }

//在流中當前位置mark

//在我們的ByteArrayInputStream中就是將當前位置賦給mark變量。

//讀取流中的字節就是讀取字節數組中當前位置向後的的字節。

 

    public synchronized void reset() {

                   pos = mark;

    }

         //重置流。即回到mark的位置。

 

    public void close() throws IOException {   }

         //關閉ByteArrayInputStream不會產生任何動作。爲什麼?仔細考慮吧!!

}

 

上面我們分3小節講了裝飾器模式中的公共父類(對應於輸入字節流的InputStream)、Decorator(對應於輸入字節流的FilterInputStream)和基本被裝飾對象(對應於輸入字節流的媒體字節流)。下面我們就要講述裝飾器模式中的具體的包裝器(對應於輸入字節流的包裝器流)。

4 BufferedInputStream

4.1原理及其在軟件硬件中的應用

       1.read――read(byte[] ,int , int)

       2.BufferedInputStream

       3.《由一個簡單的程序談起》

       4. Cache

       5.Pool

       6.Spling Printer

       (最近比較忙,不講了!)

4.2 BufferedInputStream源代碼分析

 

package java.io;

 

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

 

//該類主要完成對被包裝流,加上一個緩存的功能

public class BufferedInputStream extends FilterInputStream {

    private static int defaultBufferSize = 8192;                                      //默認緩存的大小

    protected volatile byte buf[];                                                            //內部的緩存

    protected int count;                                                                                            //buffer的大小

    protected int pos;                                                                               //buffercursor的位置

    protected int markpos = -1;                                                                     //mark的位置

    protected int marklimit;                                                                            //mark的範圍

 

//原子性更新。和一致性編程相關

    private static final

        AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =

        AtomicReferenceFieldUpdater.newUpdater (BufferedInputStream.class,  byte[].class, "buf");

 

    private InputStream getInIfOpen() throws IOException {  //檢查輸入流是否關閉,同時返回被包裝流

        InputStream input = in;

         if (input == null)    throw new IOException("Stream closed");

        return input;

    }

 

    private byte[] getBufIfOpen() throws IOException {                       //檢查buffer的狀態,同時返回緩存

        byte[] buffer = buf;

         if (buffer == null)   throw new IOException("Stream closed");            //不太可能發生的狀態

        return buffer;

    }

 

    public BufferedInputStream(InputStream in) {                               //構造器

                   this(in, defaultBufferSize);                                                              //指定默認長度的buffer

    }

 

    public BufferedInputStream(InputStream in, int size) {                           //構造器

                   super(in);

        if (size <= 0) {                                                                                         //檢查輸入參數

            throw new IllegalArgumentException("Buffer size <= 0");

        }

                   buf = new byte[size];                                                                     //創建指定長度的buffer

    }

 

         //從流中讀取數據,填充如緩存中。

    private void fill() throws IOException {

        byte[] buffer = getBufIfOpen();                            //得到buffer

         if (markpos < 0)

             pos = 0;                                                             //mark位置小於0,此時pos0

         else if (pos >= buffer.length)                               //pos大於buffer的長度

             if (markpos > 0) {        

                   int sz = pos - markpos;                            //

                   System.arraycopy(buffer, markpos, buffer, 0, sz);

                   pos = sz;

                   markpos = 0;

             } else if (buffer.length >= marklimit) {                 //buffer的長度大於marklimit時,mark失效

                   markpos = -1;                                                   //

                   pos = 0;                                                             //丟棄buffer中的內容

             } else {                                                                         //buffer的長度小於marklimit時對buffer擴容

                   int nsz = pos * 2;

                   if (nsz > marklimit)           nsz = marklimit;//擴容爲原來的2倍,太大則爲marklimit大小

                   byte nbuf[] = new byte[nsz];                    

                   System.arraycopy(buffer, 0, nbuf, 0, pos);        //buffer中的字節拷貝如擴容後的buf

                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {

                                                                                                                         //buffer在被操作時,不能取代此buffer

                    throw new IOException("Stream closed");

                }

                buffer = nbuf;                                                               //將新buf賦值給buffer

             }

        count = pos;

         int n = getInIfOpen().read(buffer, pos, buffer.length - pos);

        if (n > 0)     count = n + pos;

    }

 

    public synchronized int read() throws IOException { //讀取下一個字節

         if (pos >= count) {                                                                 //到達buffer的末端

             fill();                                                                    //就從流中讀取數據,填充buffer

             if (pos >= count)  return -1;                                //讀過一次,沒有數據則返回-1

         }

         return getBufIfOpen()[pos++] & 0xff;                           //返回buffer中下一個位置的字節

    }

 

    private int read1(byte[] b, int off, int len) throws IOException {                 //將數據從流中讀入buffer

         int avail = count - pos;                                                                             //buffer中還剩的可讀字符

         if (avail <= 0) {                                                                                        //buffer中沒有可以讀取的數據時

             if (len >= getBufIfOpen().length && markpos < 0) {             //將輸入流中的字節讀入b

                   return getInIfOpen().read(b, off, len);

             }

             fill();                                                                                                //填充

             avail = count - pos;

             if (avail <= 0) return -1;

         }

         int cnt = (avail < len) ? avail : len;                                                  //從流中讀取後,檢查可以讀取的數目

         System.arraycopy(getBufIfOpen(), pos, b, off, cnt);            //將當前buffer中的字節放入b的末端

         pos += cnt;

         return cnt;

    }

 

 

    public synchronized int read(byte b[], int off, int len)throws IOException {

        getBufIfOpen();                                                                             // 檢查buffer是否open

        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {            //檢查輸入參數是否正確

             throw new IndexOutOfBoundsException();

         } else if (len == 0) {

            return 0;

        }

         int n = 0;

        for (;;) {

            int nread = read1(b, off + n, len - n);

            if (nread <= 0)     return (n == 0) ? nread : n;

            n += nread;

            if (n >= len)     return n;

            // if not closed but no bytes available, return

            InputStream input = in;

            if (input != null && input.available() <= 0)     return n;

        }

    }

 

 

    public synchronized long skip(long n) throws IOException {

        getBufIfOpen();                                        // 檢查buffer是否關閉

         if (n <= 0) {    return 0;      }                 //檢查輸入參數是否正確

         long avail = count - pos;                    //buffered中可以讀取字節的數目

        if (avail <= 0) {                                          //可以讀取的小於0,則從流中讀取

            if (markpos <0)  return getInIfOpen().skip(n); //mark小於0,則mark在流中      

            fill();                                  // 從流中讀取數據,填充緩衝區。

            avail = count - pos;                                   //可以讀的取字節爲buffer的容量減當前位置

            if (avail <= 0)     return 0;

        }       

        long skipped = (avail < n) ? avail : n;      

        pos += skipped;                                       //當前位置改變

        return skipped;

    }

 

    public synchronized int available() throws IOException {

                   return getInIfOpen().available() + (count - pos);                 

    }

         //該方法不會block!返回流中可以讀取的字節的數目。

         //該方法的返回值爲緩存中的可讀字節數目加流中可讀字節數目的和

 

    public synchronized void mark(int readlimit) {  //當前位置處爲mark位置

         marklimit = readlimit;

         markpos = pos;

    }

 

    public synchronized void reset() throws IOException {

        getBufIfOpen(); // 緩衝去關閉了,肯定就拋出異常!程序設計中經常的手段

                   if (markpos < 0)     throw new IOException("Resetting to invalid mark");

                   pos = markpos;

    }

 

    public boolean markSupported() {           //該流和ByteArrayInputStream一樣都支持mark

                   return true;

    }

 

         //關閉當前流同時釋放相應的系統資源。

    public void close() throws IOException {

        byte[] buffer;

        while ( (buffer = buf) != null) {

            if (bufUpdater.compareAndSet(this, buffer, null)) {

                InputStream input = in;

                in = null;

                if (input != null)    input.close();

                return;

            }

            // Else retry in case a new buf was CASed in fill()

        }

    }

}

PipedInputStream/PipedOutputStream談起

       江蘇 無錫 繆小東

本篇主要從分析PipeInputStremPipedOutputStream談起。談及軟件設計的變化,以及如何將軟件拆分、組合,適配……

1 源代碼分析

       下面將詳細分析PipedInputStreamPipedOutputStream的源代碼。

1.1 PipedInputStream

 

package java.io;

//PipedInputStream必須和PipedOutputStream聯合使用。即必須連接輸入部分。

//其原理爲:PipedInputStream內部有一個Buffer

//PipedInputStream可以使用InputStream的方法讀取其Buffer中的字節。

//PipedInputStreamBuffer中的字節是PipedOutputStream調用PipedInputStream的方法放入的。

 

public class PipedInputStream extends InputStream {

    boolean closedByWriter = false;                                                             //標識有讀取方或寫入方關閉

    volatile boolean closedByReader = false;

    boolean connected = false;                                                                     //是否建立連接

    Thread readSide;                                                                                             //標識哪個線程

    Thread writeSide;

 

    protected static final int PIPE_SIZE = 1024;                         //緩衝區的默認大小

    protected byte buffer[] = new byte[PIPE_SIZE];                  //緩衝區

    protected int in = -1;               //下一個寫入字節的位置。0代表空,in==out代表滿

    protected int out = 0;               //下一個讀取字節的位置

 

    public PipedInputStream(PipedOutputStream src) throws IOException {                //給定源的輸入流

                   connect(src);

    }

 

    public PipedInputStream() {    }                                                //默認構造器,下部一定要connect

 

    public void connect(PipedOutputStream src) throws IOException {               //連接輸入源

                   src.connect(this);                                                                           //調用源的connect方法連接當前對象

    }

 

    protected synchronized void receive(int b) throws IOException {                   //只被PipedOuputStream調用

        checkStateForReceive();                                                                                 //檢查狀態,寫入

        writeSide = Thread.currentThread();                                                      //永遠是PipedOuputStream

        if (in == out)     awaitSpace();                                                           //輸入和輸出相等,等待空間

         if (in < 0) {

             in = 0;

             out = 0;

         }

         buffer[in++] = (byte)(b & 0xFF);                                                             //放入buffer相應的位置

         if (in >= buffer.length) {      in = 0;         }                                             //in0表示buffer已空

    }

 

    synchronized void receive(byte b[], int off, int len)  throws IOException {

        checkStateForReceive();

        writeSide = Thread.currentThread();                                   //PipedOutputStream可以看出

        int bytesToTransfer = len;

        while (bytesToTransfer > 0) {

            if (in == out)    awaitSpace();                                 //滿了,會通知讀取的;空會通知寫入

            int nextTransferAmount = 0;

            if (out < in) {

                nextTransferAmount = buffer.length - in;

            } else if (in < out) {

                if (in == -1) {

                    in = out = 0;

                    nextTransferAmount = buffer.length - in;

                } else {

                    nextTransferAmount = out - in;

                }

            }

            if (nextTransferAmount > bytesToTransfer)     nextTransferAmount = bytesToTransfer;

            assert(nextTransferAmount > 0);

            System.arraycopy(b, off, buffer, in, nextTransferAmount);

            bytesToTransfer -= nextTransferAmount;

            off += nextTransferAmount;

            in += nextTransferAmount;

            if (in >= buffer.length) {     in = 0;      }

        }

    }

 

    private void checkStateForReceive() throws IOException {                           //檢查當前狀態,等待輸入

        if (!connected) {

            throw new IOException("Pipe not connected");

        } else if (closedByWriter || closedByReader) {

             throw new IOException("Pipe closed");

         } else if (readSide != null && !readSide.isAlive()) {

            throw new IOException("Read end dead");

        }

    }

 

    private void awaitSpace() throws IOException {                                              //Buffer已滿,等待一段時間

         while (in == out) {                                                                                             //in==out表示滿了,沒有空間

             checkStateForReceive();                                                                       //檢查接受端的狀態

             notifyAll();                                                                                  //通知讀取端

             try {

                 wait(1000);

             } catch (InterruptedException ex) {

                   throw new java.io.InterruptedIOException();

             }

         }

    }

 

    synchronized void receivedLast() {                  //通知所有等待的線程()已經接受到最後的字節

         closedByWriter = true;                             //

         notifyAll();

    }

 

    public synchronized int read()  throws IOException {

        if (!connected) {                                                                              //檢查一些內部狀態

            throw new IOException("Pipe not connected");

        } else if (closedByReader) {

             throw new IOException("Pipe closed");

         } else if (writeSide != null && !writeSide.isAlive()&& !closedByWriter && (in < 0)) {

            throw new IOException("Write end dead");

         }

        readSide = Thread.currentThread();                                            //當前線程讀取

         int trials = 2;                                                                                             //重複兩次????

         while (in < 0) {

             if (closedByWriter) {              return -1;        }                 //輸入斷關閉返回-1

             if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {          //狀態錯誤

                   throw new IOException("Pipe broken");

             }

             notifyAll();                                                             // 空了,通知寫入端可以寫入

             try {

                 wait(1000);

             } catch (InterruptedException ex) {

                   throw new java.io.InterruptedIOException();

             }

        }

         int ret = buffer[out++] & 0xFF;                                                        //

         if (out >= buffer.length) {             out = 0;                }

         if (in == out) {           in = -1;                 }                             //沒有任何字節

         return ret;

    }

 

    public synchronized int read(byte b[], int off, int len)  throws IOException {

     if (b == null) {                                                                                 //檢查輸入參數的正確性

         throw new NullPointerException();

     } else if (off < 0 || len < 0 || len > b.length - off) {

         throw new IndexOutOfBoundsException();

     } else if (len == 0) {

         return 0;

     }

     int c = read();                                                                                 //讀取下一個

     if (c < 0) {    return -1;       }                                             //已經到達末尾了,返回-1

     b[off] = (byte) c;                                                                    //放入外部buffer

     int rlen = 1;                                                                            //return-len

     while ((in >= 0) && (--len > 0)) {                                          //下一個in存在,且沒有到達len

         b[off + rlen] = buffer[out++];                                         //依次放入外部buffer

         rlen++;

         if (out >= buffer.length) {         out = 0;           }        //讀到buffer的末尾,返回頭部

         if (in == out) {     in = -1;      }               //讀、寫位置一致時,表示沒有數據

     }

     return rlen;                                                                            //返回填充的長度

    }

 

    public synchronized int available() throws IOException {             //返回還有多少字節可以讀取

         if(in < 0)

             return 0;                                                                                         //到達末端,沒有字節

         else if(in == out)

             return buffer.length;                                                               //寫入的和讀出的一致,表示滿

         else if (in > out)

             return in - out;                                                                                 //寫入的大於讀出

         else

             return in + buffer.length - out;                                                //寫入的小於讀出的

    }

 

    public void close()  throws IOException {                //關閉當前流,同時釋放與其相關的資源

         closedByReader = true;                                             //表示由輸入流關閉

        synchronized (this) {     in = -1;    }        //同步化當前對象,in-1

    }

}

 

1.2 PipedOutputStream

// PipedOutputStream一般必須和一個PipedInputStream連接。共同構成一個pipe

//它們的職能是:

 

package java.io;

import java.io.*;

 

public class PipedOutputStream extends OutputStream {

    private PipedInputStream sink;                //包含一個PipedInputStream

 

    public PipedOutputStream(PipedInputStream snk)throws IOException {       //帶有目的地的構造器

                   connect(snk);

    }

   

    public PipedOutputStream() {  }                      //默認構造器,必須使用下面的connect方法連接

   

    public synchronized void connect(PipedInputStream snk) throws IOException {

        if (snk == null) {                                                                    //檢查輸入參數的正確性

            throw new NullPointerException();

        } else if (sink != null || snk.connected) {

             throw new IOException("Already connected");

         }

         sink = snk;                                                                           //一系列初始化工作

         snk.in = -1;

         snk.out = 0;

        snk.connected = true;

    }

 

    public void write(int b) throws IOException {                        //向流中寫入數據

        if (sink == null) {    throw new IOException("Pipe not connected");      }

         sink.receive(b);            //本質上是,調用PipedInputStreamreceive方法接受此字節

    }

 

    public void write(byte b[], int off, int len) throws IOException {

        if (sink == null) {                                                                   //首先檢查輸入參數的正確性

            throw new IOException("Pipe not connected");

        } else if (b == null) {

             throw new NullPointerException();

         } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {

             throw new IndexOutOfBoundsException();

         } else if (len == 0) {

             return;

         }

         sink.receive(b, off, len);                                                                 //調用PipedInputStreamreceive方法接受

    }

 

    public synchronized void flush() throws IOException {                 //flush輸出流

         if (sink != null) {

            synchronized (sink) {     sink.notifyAll();     } //本質是通知輸入流,可以讀取

         }

    }

 

    public void close()  throws IOException {                         //關閉流同時釋放相關資源

         if (sink != null) {    sink.receivedLast();         }

    }

}

 

2 Buffer的狀態

       上圖是PipedInputStream中緩存的狀態圖。在程序中我們利用了byte數組,循環地向其中寫入數據,寫入有一個cursorin),讀出也有一個cursorout)。上圖表示inout不同位置時,buffer中的各個位置的狀態。藍色的代表可以讀取的字節。白色的表示此位置沒有字節,或者此位置已經被PipedInputStream讀取了。

3 交互簡圖

       下圖是從源代碼部分轉換過來的關於PipedInputStreamPipedOutputStream的交互圖。

 

 

       從圖中可以看出:

1.         整個PipedInputStream是這對管道的核心。管道本身是一個byte的數組。

2.         PipedOutputStream對象通過Delegate方法複用PipedInputStream,同時屏蔽了其中的讀取的方法,我們僅僅可以構造PipedOutputStream對象。(從這一點可以看出Delegate複用比繼承複用的優越性了!)從設計模式的角度更象Adapter――PipedInputStream本身提供讀取和寫入的功能,將寫入的功能適配到OutputStream,就成爲一個PipedOutputStream。這樣就形成一個類,適配後形成兩種功能的類。

3.         調用PipedOutputStream的連接方法實際就是調用PipedInputStream的連接方法。

4.         調用PipedOutputStream的寫相關的方法實際就是調用PipedInputStream的對應方法。

以上也是一種適配,將管道的概念適配到流的概念,同時將兩者的職能分開。

 

4 Chanel放入PipedOutputStream

       上面的例子中,Chanel放在PipedInputStream中,我們仔細思考後可以順理成章地將其Chanel放入PipedOutputStream中。請注意synchronized方法是得到哪個字節流的鎖!!

5 Chanel移出的一個例子

       在上面兩個例子中Buffer要麼在寫入對象的內部,要麼在讀取對象的內部。主要通過適配該對象的方法,達到自己的需求而已。下面是一個一般的例子――將Chanel移出,Chanel提供了寫入與讀取的功能。這也完全合乎OO的“Single Responsibility Protocol――SRP”。輸入部分使用Delegate複用此Chanel,將其適配至InputStreamOutputStream。下面是簡單的Source code

//PipedChanel.java

       import java.io.IOException ;

 

public class PipedChanel {

    protected static final int PIPE_SIZE = 1024;

    protected byte buffer[] = new byte[PIPE_SIZE];   

    protected int in = -1;

protected int out = 0;

   

    public PipedChanel(){  }       

    public PipedChanel(int size){

           buffer = new byte[size]  ; 

    }   

   

    public synchronized int read() throws IOException {    }       

    public synchronized int read(byte b[], int off, int len)  throws IOException {    }   

    public synchronized int available() throws IOException {}   

    public synchronized void close()  throws IOException {}       

   

public synchronized void write(int b)  throws IOException {}

public synchronized void write(byte b[]) throws IOException {}

    public synchronized void write(byte b[], int off, int len) throws IOException {}         

public synchronized void flush() throws IOException {}       

 

    public void waitWhileFull(){    }            //Chanel已經滿了,寫線程等待

public void waitWhileEmpty{    }        //Chanel爲空,讀取線程等待

//以上是兩個操作Chanel時的狀態相關的方法。

//是一致性編程部分,典型的設計模式。

//這兩個方法,包含在對應讀或寫方法的最前面。

}

 

 

       // PipedChanelInputStream.java

import java.io.*;

 

public class PipedChanelInputStream extends InputStream {

       private PipedChanel chanel ;

      

       public PipedChanelInputStream(PipedChanel chanel){

              this.chanel = chanel ;

       }

      

       public int read() throws IOException {

              return chanel.read();

       }   

   

    public  int read(byte b[], int off, int len)  throws IOException {

           return chanel.read(b,off,len);

    }

   

    public  int available() throws IOException {

           return chanel.available();

    }

   

    public  void close()  throws IOException {

           chanel.close();      

    }        

      

}

 

 

       // PipedChanelOutputStream.java

import java.io.*;

 

public class PipedChanelOutputStream extends OutputStream {

       private PipedChanel chanel ;

      

       public PipedChanelOutputStream(PipedChanel chanel){

              this.chanel = chanel ;

       }

   

    public synchronized void write(int b)  throws IOException {

           chanel.write(b);     

    }

    public synchronized void write(byte b[]) throws IOException {

           chanel.write(b);     

    }

    public synchronized void write(byte b[], int off, int len) throws IOException {

           chanel.write(b,off,len); 

    }    

    public synchronized void flush() throws IOException {

           chanel.flush();

    }

    public synchronized void close()  throws IOException {

           chanel.close();      

    }   

}

 

       很簡單的例子。我們可以體會適配器模式,可以體會軟件設計的靈活性……

       上面的關於PipedInputStreamPipedOutputStream的例子,本質上是對一個Chanel的幾個不同的適配。Chanel作爲一種編程模式,在軟件設計中有極其廣泛的應用。下面一節是JMS的簡潔闡述!

       以上的例子其實是一個典型的使用適配器。

 

6 JMS的架構

       JMSJ2EE部分的面向消息中間件的APIJMSQueueTopic某種意義上就是我們上面Chanel移到網絡的其它一段――服務器上的一個例子。同時該Chanel得到了很多強化。如:1.支持交易;2.支持持久化……

       J2EEJMS是一個比較重要的方向,大型的企業應用中都會使用。不過J2EE中給出了其API,背後的理念還是相當豐富的!(具體細節以後會有相關文章!!唉,還是因爲忙!!)

 

 
發佈了32 篇原創文章 · 獲贊 1 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章