1.類族
java.io包中包含了java基於流的IO類庫的整個類族,其中包含了字符流和字節流兩種流,這裏來看一看整個類族結構。
1.1 類圖
借用幾張網絡圖片來說明(圖片來自 http://blog.csdn.net/zhangerqing/article/details/8466532 )
1.1.1 輸入字節流
1.1.2 輸出字節流
1.1.3 輸入字符流
1.1.4 輸出字符流
1.2 類族說明
java的io包中包含了字符流與字節流兩種流,而兩種流又各自包含了輸入輸出。其中:
1)輸入字節流全部繼承自InputStream
2)輸出字節流全部繼承自OutputStream
3)輸入字符流全部繼承自Reader
4)輸出字符流全部繼承自Writer
2.抽象類
剛纔說過了IO包擁有四個模塊的類族結構,而這四個類族的父類均爲抽象類,這裏我們簡單看一下這四個抽象類的實現。
事實上這四個抽象類的實現方式和以前的文章中提到的設計基本類似,由於IO的讀寫來源不同,所以抽象類並不去實現最根本的read、write等方法,而是將其留給子類實現:
public abstract int read() throws IOException;
而對於這些基本操作的一些封裝則被抽象類實現了:
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;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
這裏read(byte b[], int off, int len)調用了最基本的read方法,並截取byte數組賦值於傳入的b[];
write的實現基本類似。
3.裝飾器模式
我們用一段代碼來表達java IO中 裝飾器模式的實例:
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
我們看到這裏InputStreamReader的構造器傳入了一個InputStream,事實上他並不在乎這個InputStream到底是來源於socket還是object還是file還是其他。
public InputStreamReader(InputStream in, String charsetName)
throws UnsupportedEncodingException
{
super(in);
if (charsetName == null)
throw new NullPointerException("charsetName");
sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
}
同理,BufferedReader中也只傳入了一個Reader:
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
這樣的設計讓IO中各種類可以自由組裝,不同的來源的字節流可以傳入字符流中,不同的流也可以傳入緩衝中。
4.以偏看全
4.1 BufferedReader::read()
話不多說直接上源碼
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
for (;;) {
if (nextChar >= nChars) {
fill();
if (nextChar >= nChars)
return -1;
}
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
continue;
}
}
return cb[nextChar++];
}
}
}
首先,這個read方法是線程安全的;其次,它先調用open打開流,再然後去讀取cb數組。
這裏值得注意的是這個fill方法,可以說,這個fill方法的作用就是整個緩衝類的作用。
/**
* Fills the input buffer, taking the mark into account if it is valid.
*/
private void fill() throws IOException {
int dst;
if (markedChar <= UNMARKED) {
/* No mark */
dst = 0;
} else {
/* Marked */
int delta = nextChar - markedChar;
if (delta >= readAheadLimit) {
/* Gone past read-ahead limit: Invalidate mark */
markedChar = INVALIDATED;
readAheadLimit = 0;
dst = 0;
} else {
if (readAheadLimit <= cb.length) {
/* Shuffle in the current buffer */
System.arraycopy(cb, markedChar, cb, 0, delta);
markedChar = 0;
dst = delta;
} else {
/* Reallocate buffer to accommodate read-ahead limit */
char ncb[] = new char[readAheadLimit];
System.arraycopy(cb, markedChar, ncb, 0, delta);
cb = ncb;
markedChar = 0;
dst = delta;
}
nextChar = nChars = delta;
}
}
int n;
do {
n = in.read(cb, dst, cb.length - dst);
} while (n == 0);
if (n > 0) {
nChars = dst + n;
nextChar = dst;
}
}
前面的部分,就是根據各種參數,以及cb裏面是否爲空等等各種信息,去生成一個新的cb,這個新的cb就是接下去讀的緩存數組。
然後就調用下一層(組合in)的read方法去讀取。
整個類的讀取方式簡而言之就是把一次性的讀取根據緩衝區的大小分成多次讀取。
4.2 FileInputStream
由於是以偏概全的讀代碼,我們在上一節看了一下裝飾器上層封裝的緩衝區類的使用。這裏就讀一下最底層的使用。
其實這一層的代碼閱讀只是交代一下,並沒有什麼實質的意義,因爲其最終都是用的native方法:
/**
* Reads a byte of data from this input stream. This method blocks
* if no input is yet available.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* file is reached.
* @exception IOException if an I/O error occurs.
*/
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
既然是JDK的閱讀,自然就不超綱去閱讀JVM層完成的native代碼了。計劃着下一篇閱讀一下NIO之後就暫時暫停JDK源碼的閱讀,開始把矛頭轉向JVM的閱讀中去。
5.總結
感覺也沒什麼好總結的。下一篇JDK講NIO,再下一篇計劃着開始看JVM,之前的jamVM源碼的閱讀也是半途而廢的呢。