javaIO(1):OutputStream和FileOutputStream原始碼分析及“裝飾者模式”在IO中的應用
前言
一,IO體系
從現在起,我們將基於JDK1.8詳細介紹java.io包中的關於輸入輸出有關的類。瞭解過這個包的都知道,裡面的類繼承關係錯綜複雜,光是弄清楚這些類的關係就夠喝一壺的了。說實話,我也沒有什麼好的方法來一下子就能弄清這些類,但是如果你瞭解“裝飾者模式”的話,瞭解了其中一類流體系的話,就能類比記憶其他體系。其中的類大致分成5類體系,其中流體系有4類:
1,File類。包裡面有一個單獨的File類,這個類是檔案和目錄路徑名的抽象表示形式。這個類裡面包含了很多與檔案或路徑有關的方法,如:建立和刪除檔案或者路徑,獲取檔案或路徑的屬性,判斷檔案或路徑是否具有一些性質等。儘管輸入輸出裝置有很多,但是操作最多的還是硬碟,而資料在硬碟上的表現形式就是檔案,即File。
2,OutputStream及其子類:輸出位元組流。這個體系將資料按照位元組格式寫入到輸出裝置(硬碟或螢幕等)。
3,InputStream及其子類:輸入位元組流。這個體系讀取不同輸入裝置(鍵盤,硬碟等)的位元組資料來源。
4,Writer及其子類:輸出字元流。這個體系將資料按照字元格式寫入到輸出裝置(硬碟或螢幕等)。
5,Reader及其子類:輸入字元流。這個體系將資料按照字元格式寫入到輸出裝置(硬碟或螢幕等)。
二,IO中的“裝飾者模式”
本文先講OutputStream位元組輸出流體系,下面是這個體系的繼承關係圖。其中1,2,3,4是OutputStream的直接子類,他們分別實現了父類的write()等方法,而且寫的形式和目的地各不相同,用來完成不同的任務。還有一個直接子類FilterOutputStream,它及其子類5,6,7就構成了“裝飾者模式”,比如子類5:BufferedOutputStream,它可以接受1,2,3,4等直接子類,完成特定任務的同時,使用了緩衝區,提高了效率。
正文
File類的使用很簡單,不再贅述。本文說一下OutputStream位元組輸出流及其子類的部分原始碼,以及“裝飾者模式”的應用。
一,OutputStream原始碼
package java.io;
// OutputStream是所有位元組輸出流的超類,並實現2個介面,這兩個介面種分別有一個方法close()和flush()
public abstract class OutputStream implements Closeable, Flushable {
/*
將指定位元組寫入此檔案輸出流,子類需實現該方法。write 的常規協定是:向輸出流寫入一個位元組。要寫入的位元組是引數 b 的八個低位。b 的 24 個高位將被忽略。
*/
public abstract void write(int b) throws IOException;
// 將 b.length 個位元組從指定 byte 陣列寫入此檔案輸出流中。
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
// 將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此檔案輸出流。
public void write(byte b[], int off, int len) throws IOException {
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;
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]); // 去呼叫wirte(int b)方法一個一個寫
}
}
/* 重新整理此輸出流並強制寫出所有緩衝的輸出位元組。flush 的常規協定是:如果此輸出流的實現已經緩衝了以前寫入的任何位元組,則呼叫此方法指示應將這些位元組立即寫入它們預期的目標。*/
public void flush() throws IOException {
}
/*
關閉此輸出流並釋放與此流有關的所有系統資源。close 的常規協定是:該方法將關閉輸出流。關閉的流不能執行輸出操作,也不能重新開啟。
*/
public void close() throws IOException {
}
}
二,FileOutputStream原始碼
OutputStream的直接子類,完成特定功能,即以位元組的形式將資料寫入到檔案中。較常用。
package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
/**
* 檔案輸出流用於將位元組資料寫入到檔案中。
*
* 該流可以用作寫影象資料。要寫字元,則考慮FileWriter
*
* 是OutputStream的直接子類
*/
public class FileOutputStream extends OutputStream
{
/**
* 開啟檔案控制代碼
*/
private final FileDescriptor fd;
/**
* 是否在檔案末尾追加
*/
private final boolean append;
/**
* 引用物件
*/
private FileChannel channel;
/**
* 檔案路徑
*/
private final String path;
private final Object closeLock = new Object();
private volatile boolean closed = false;
// 構造方法1,傳入表示檔案路徑的字串,將字串初始化為File物件,傳給構造方法4
public FileOutputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, false);
}
// 構造方法2,傳入表示檔案路徑的字串和是否追加標識,將字串初始化為File物件,傳給構造方法4
public FileOutputStream(String name, boolean append)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, append);
}
// 構造方法3,傳入File物件,呼叫構造方法4
public FileOutputStream(File file) throws FileNotFoundException {
this(file, false);
}
// 構造方法4,前3個構造方法都呼叫這個構造方法
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkWrite(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
this.fd = new FileDescriptor();
fd.attach(this);
this.append = append;
this.path = name;
open(name, append); // open呼叫native方法,開啟指定的檔案
}
// 建立一個向指定檔案描述符處寫入資料的輸出檔案流
public FileOutputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkWrite(fdObj);
}
this.fd = fdObj;
this.append = false;
this.path = null;
fd.attach(this);
}
// 實現父類的write方法
public void write(int b) throws IOException {
write(b, append); // 呼叫native方法,一次寫一個位元組。
}
// 重寫了父類的write(byte[])方法
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, append); // 呼叫native方法,寫位元組陣列
}
// 重寫了父類的write方法
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len, append); // 呼叫native方法,寫位元組陣列一部分
}
// 重寫了父類的close()方法,同樣呼叫native方法實現資源關閉
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
// flush繼承自父類,父類的flush方法啥都不做,自然FileOutputStream的flush方法也是啥也不做。
三,ByteArrayOutputStream和ObjectOutputStream等
這2個直接子類和其他直接子類同樣實現了特定的write()方式,程式碼不再貼出來了。
四,FilterOutputStream,抽象裝飾者類
該類繼承自OutputStream,但是它上面都不幹,只持有父類的物件,並呼叫父類的同名方法。
package java.io;
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); // 呼叫父類的同名方法
}
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
throw new IndexOutOfBoundsException();
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
public void flush() throws IOException {
out.flush(); // 呼叫父類的同名方法
}
@SuppressWarnings("try")
public void close() throws IOException {
try (OutputStream ostream = out) {
flush();
}
}
}
五,BufferedOutputStream,具體裝飾者類
該類是繼承自FilterOutputStream,自然也就繼承了父類的OutputStream型別成員變數out。也就可以為OutputStream的直接子類提供特定的“裝飾”,即緩衝區。
package java.io;
public class BufferedOutputStream extends FilterOutputStream {
protected byte buf[]; // 內部緩衝區
protected int count; // 緩衝區中的有效位元組數
public BufferedOutputStream(OutputStream out) {
this(out, 8192); // 構造具有OutputStream物件和固定大小緩衝區的物件
}
public BufferedOutputStream(OutputStream out, int size) {
super(out); // 呼叫父類的構造器傳入OutputStream物件
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size]; // 初始化自定義大小的緩衝區
}
private void flushBuffer() throws IOException { // 重新整理緩衝區方法
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
/*
BufferedOutputStream的write()方法底層雖然也是呼叫了OutputStream的write()方法,但是這個write()方法在OutputStream的write()方法基礎上添加了快取的功能,即對原write()方法進行了“裝飾”,從而提高了效率。
*/
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer(); // 緩衝區滿了就重新整理
}
buf[count++] = (byte)b; // 快取位元組資料
}
// 同理,新write()方法對老write()方法進行了“裝飾”,即功能的增強。
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
}
六,DataOutputStream和PrintStream等具體裝飾者類。
同BufferedOutputStream對OutputStream進行“緩衝區裝飾”,還有其他的很多具體裝飾者類對OutputStream進行各種各樣的“裝飾”。如:DataOutputStream以適當方式將基本 Java 資料型別寫入輸出流中,PrintStream為其他輸出流添加了功能,使它們能夠方便地列印各種資料值表示形式。
程式碼不再貼出來了。
總結
現在,我們總結了OutputStream體系的所有類,發現如下幾條規律:
1,OutputStream作為超類,定義了該體系最基本的方法。
2,OutputStream的直接子類(除了FilterOutputStream),實現了各種情況下所需的write()方式。
3,FilterOutputStream類及其子類是對2中各直接子類的“裝飾”,目的是提供額外的功能。
經過以上總結,再類比到InputStream,Reader,Writer體系就很容易理解啦。