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語言的編譯器中使用它。很多程序員可能一輩子都不需要。
|
- OutputStream
- ByteArrayOutputStream
- FileOutputStream
- FilterOutputStream
- BufferedOutputStream
- DataOutputStream
- PrintStream
- ObjectOutputStream
- PipedOutputStream
功能
|
如何構造
|
|
怎樣使用
|
||
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.4 字節流與字符流
從上面我們可以看出IO中的字節流是極其複雜的,存在大量的類,到目前爲止還沒有真正使用它們,使用它們應該也是極其複雜的吧!JDK1.1後Sun對IO庫進行了重大的改進。看到Reader和Writer類時,大多數人的第一個感覺(不要太相信感覺哦!感覺也許會欺騙你的!)就是它們是用來替換原來的InputStream和OutputStream類。有新的類,幹嗎還使用舊的呢!?但實情並非如此。儘管Sun不建議使用原始的流庫中的某些功能,但原來的流依然得到了保留,不僅爲了保持向後兼容,主要原因是新庫不是舊庫的替代,而是對舊庫的增強。從以下兩點可以明顯地看出:
(1) 在老式的類層次結構里加入了新的類,這表明 Sun公司沒有放棄老式流庫的意圖。
(2) 在許多情況下,新庫中類的使用需要聯合老結構中的類。爲達到這個目的,需要使用一些“橋”類,如:InputStreamReader將一個InputStream轉換成Reader;OutputStreamWriter將一個OutputStream轉換成Writer。
那麼Sun爲什麼在Java 1.1裏添加了Reader和Writer層次,最重要的原因便是國際化(Internationalization――i18n)的需求。老式IO流層次結構只支持8位字節流,不能很好地控制16位的Unicode字符。Java本身支持Unicode,Sun又一致吹噓其支持Unicode,因此有必要實現一個支持Unicode的流的層次結構,所以出現了Reader和Writer層次,以提供對所有IO操作中的Unicode的支持。除此之外,新庫也對速度進行了優化,可比舊庫更快地運行。
8位的字節流和16位的字符流的對應關係,可以從ByteInputStream/ByteOutputStream與CharArrayInputStream/CharArrayOutputStream的對應關係中看出端倪。(還沒看出來啊!趕緊去看看Java的基本數據類型)。
因此在Java的IO體系中存在字節流和字符流的對應關係。下面就看看字符流吧!
3.5 IO中的輸入字符流
下面是IO中輸入字符流的繼承圖。
- Reader
- BufferedReader
- LineNumberReader
- CharArrayReader
- FilterReader
- PushbackReader
- InputStreamReader
- FileReader
- PipedReader
- StringReader
在上面的關係圖中可以看出:1.Reader是所有的輸入字符流的父類,它是一個抽象類。2.CharReader、StringReader是兩種基本的介質流,它們分別將Char數組、String中讀取數據。PipedReader是從與其它線程共用的管道中讀取數據。3. BufferedReader很明顯就是一個裝飾器,它和其子類負責裝飾其它Reader對象。4.FilterReader是所有自定義具體裝飾流的父類,其子類PushbackReader對Reader對象進行裝飾,會增加一個行號。5.InputStreamReader是一個連接字節流和字符流的橋樑,它將字節流轉變爲字符流。FileReader可以說是一個達到此功能、常用的工具類,在其源代碼中明顯使用了將FileInputStream轉變爲Reader的方法。我們可以從這個類中得到一定的技巧。
Reader中各個類的用途和使用方法基本和InputStream中的類使用一致。後面會有Reader與InputStream的對應關係。
3.6 IO中的輸出字符流
下面是IO中輸出字符流的繼承圖。
- Writer
- BufferedWriter
- CharArrayWriter
- FilterWriter
- OutputStreamWriter
- FileWriter
- PipedWriter
- PrintWriter
- StringWriter
在上面的關係圖中可以看出:1.Writer是所有的輸出字符流的父類,它是一個抽象類。2. CharArrayWriter、StringWriter是兩種基本的介質流,它們分別向Char數組、String中寫入數據。PipedWriter是向與其它線程共用的管道中寫入數據, 3. BufferedWriter是一個裝飾器爲Writer提供緩衝功能。4.PrintWriter和PrintStream極其類似,功能和使用也非常相似。5.OutputStreamWriter是OutputStream到Writer轉換的橋樑,它的子類FileWriter其實就是一個實現此功能的具體類(具體可以研究一下Source Code)。功能和使用和OutputStream極其類似,後面會有它們的對應圖。
3.7字符流的輸入與輸出的對應
下圖爲字符流的輸入與輸出的對應關係圖:
對應關係和字節流的輸入輸出基本一致,不必多說了吧!在下面的源代碼閱讀部分會仔細研究一些!
3.8字節流和字符流的對應
Java的IO中存在輸入、輸出的對應和字節流和字符流的對應,下面就看看字節流和字符流的對應吧!
3.8.1輸入的對應
下圖是IO中字節輸入流與字符輸入流的對應圖:
藍色的表示對應的部分,紅色的表示不對應的部分。至於爲什麼不對應還是你自己多看看源代碼、多考慮考慮吧!還要強調一點就是即使對應,它們的繼承關係也是不太對應的。
3.8.2輸出的對應
下圖是IO中字節輸出流與字符輸出流的對應圖:
不多說了!等講述了Adapter和Decorator模式會基本明白IO架構的!通過幾個實例一般就可以使用了!
從InputStream到ByteArrayInputStream
江蘇 無錫 繆小東
本篇主要分析:1.如何將byte數組適配至ByteArrayInputStream,對應與IO部分的適配器模式;2.BufferedInputStream的工作原理,對應於IO的裝飾器模式,會首先研究InputStream和FilterInputStream的源代碼,同時會將要談談軟件設計中的緩存相關的知識。後面專門一章分析PipedInputStream和PipedOutStream,簡單談談管道相關的知識,以及軟件架構的想法。
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 IO和New 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時纔可以使用此方法。
//看看mark、available和reset方法。體會爲什麼?!
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 ByteArray到ByteArrayInputStream的適配
// ByteArrayInputStream內部有一個byte類型的buffer。
//很典型的適配器模式的應用――將byte數組適配流的接口。
//下面是源代碼分析:
package java.io;
public class ByteArrayInputStream extends InputStream {
protected byte buf[]; //內部的buffer,一般通過構造器輸入
protected int pos; //當前位置的cursor。從0至byte數組的長度。
//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; //buffer中cursor的位置
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,此時pos爲0
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談起
江蘇 無錫 繆小東
本篇主要從分析PipeInputStrem和PipedOutputStream談起。談及軟件設計的變化,以及如何將軟件拆分、組合,適配……
1 源代碼分析
下面將詳細分析PipedInputStream和PipedOutputStream的源代碼。
1.1 PipedInputStream
package java.io;
//PipedInputStream必須和PipedOutputStream聯合使用。即必須連接輸入部分。
//其原理爲:PipedInputStream內部有一個Buffer,
//PipedInputStream可以使用InputStream的方法讀取其Buffer中的字節。
//PipedInputStream中Buffer中的字節是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; } //in爲0表示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); //本質上是,調用PipedInputStream的receive方法接受此字節
}
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); //調用PipedInputStream的receive方法接受
}
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數組,循環地向其中寫入數據,寫入有一個cursor(in),讀出也有一個cursor(out)。上圖表示in和out不同位置時,buffer中的各個位置的狀態。藍色的代表可以讀取的字節。白色的表示此位置沒有字節,或者此位置已經被PipedInputStream讀取了。
3 交互簡圖
下圖是從源代碼部分轉換過來的關於PipedInputStream和PipedOutputStream的交互圖。
從圖中可以看出:
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,將其適配至InputStream和OutputStream。下面是簡單的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();
}
}
很簡單的例子。我們可以體會適配器模式,可以體會軟件設計的靈活性……
上面的關於PipedInputStream和PipedOutputStream的例子,本質上是對一個Chanel的幾個不同的適配。Chanel作爲一種編程模式,在軟件設計中有極其廣泛的應用。下面一節是JMS的簡潔闡述!
以上的例子其實是一個典型的使用適配器。
6 JMS的架構
JMS爲J2EE部分的面向消息中間件的API。JMS的Queue、Topic某種意義上就是我們上面Chanel移到網絡的其它一段――服務器上的一個例子。同時該Chanel得到了很多強化。如:1.支持交易;2.支持持久化……
在J2EE中JMS是一個比較重要的方向,大型的企業應用中都會使用。不過J2EE中給出了其API,背後的理念還是相當豐富的!(具體細節以後會有相關文章!!唉,還是因爲忙!!)