Java 的輸入輸出老是給人一種很混亂的感受,要想把這個問題搞清楚,必須對各類與輸入輸出相關的類之間的關係有所瞭解。只有你瞭解了他們之間的關係,知道設計這個類的目的是什麼,才能更從容的使用他們。
字節流輸出
![java_io_write_bytes](http://static.javashuo.com/static/loading.gif)
圖1 Java 字節輸出類java
OutputStream是全部字節輸出類的超類,這是個抽象類,須要實現其中定義的 write
函數,纔能有實用的功能。git
public abstract void write(int b) throws IOException;
其它方法都是在 write
的基礎上實現的。例如這個多態的 write
:github
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]);
}
}
FileOutputStream
會將內容輸出到 File
或者 FileDescriptor
, 此類是按照字節輸出,若是想按照字符輸出,可使用FileReader
類。數組
構造器中,須要指明輸出的文件:markdown
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();
}
this.fd = new FileDescriptor();
this.append = append;
fd.incrementAndGetUseCount();
open(name, append);
}
寫入操做是一個 native
函數,與操做系統相關。app
private native void write(int b, boolean append) throws IOException;
若是對比一下字節輸入類,你會發現輸入和輸出在實現上有很大的類似性,它們是對稱的。函數
ByteArrayOutputStream 會將數據寫入字節數組中, 能夠經過 toByteArray,toString
獲得這些數據。post
初始化時,能夠指定這個數組的大小:this
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
}
寫入時,會寫入這個數組。write
會先保證數組的大小,若是不夠用,還會自動進行擴充。spa
public synchronized void write(int b) {
ensureCapacity(count + 1);
buf[count] = (byte) b;
count += 1;
}
全部有過濾功能的類的基類,例如,對輸出流進行轉化,或者添加新的功能。初始化時,須要提供一個底層的流,用於寫入數據,FilterOUtputStream
類的全部方法都是經過調用這個底層流的方法實現的。
初始化時,
protected OutputStream out;
public FilterOutputStream(OutputStream out) {
this.out = out;
}
寫入時:
public void write(int b) throws IOException {
out.write(b);
}
BufferedOutputStream 是 FilterOutputStream 的子類,提供緩衝功能,因此,你不用每寫入一個字節都要調用操做系統的write
方法,而是積累到緩衝區,而後一塊兒寫入。
緩衝區就是一個字節數組,在構造器中被初始化。
protected byte buf[];
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
當調用 write(b)
時,並不真正寫入,而是將要寫入的數據存放在緩衝區內,等緩衝區滿後,一次性寫入數據。
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
DataOutputStream 能夠按 Java 的基本類型寫入數據。寫入的原理是,將基本類型數據中的字節分離出來,而後將這些字節寫入。例如:
public final void writeBoolean(boolean v) throws IOException {
out.write(v ? 1 : 0);
incCount(1);
}
boolean 類型就是按照 0/1
的方式寫入的。
public final void writeShort(int v) throws IOException {
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
incCount(2);
}
short 是兩個字節,須要將其中的兩個字節分離出來,分別寫入,incCount
加了2. writeChar
同理,由於它也是寫入兩個字節。
浮點數比較特殊,無法直接分離出各個字節,要調用 Float
的一個靜態方法,把浮點數轉化成四個字節,再經過 writeInt
寫入。floatToInitBits
會調用一個 native
方法, 按照 IEEE 754 標準,完成其主要功能。
public final void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
管道輸出流能夠與一個管道輸入流相關聯,關聯後,共用一個緩衝區,輸出流寫入數據,輸入流讀取數據,兩者應該處於不一樣線程,不然可能出現死鎖。
原理上一篇文章在介紹 PipedInputStream 時,已經闡述。
另外,我以爲在這裏,有必要說一下那幾個用於壓縮和解壓縮的類,實現就不說了,就講下他們的功能與關係。
JAVA IO 壓縮與解壓縮
不得不說,這個API設計的真是太反直覺了。GZIP 格式的解壓和壓縮一個是 GZIPInputStream,一個是 GZIPOutputStream。而 deflate 格式的解壓和壓縮,一個是 InflaterInputStream/InflaterOutputStream,另外一個是 DeflaterInputStream/DeflaterOutputStream。當同時須要對 gzip 和 deflate 壓縮和解壓縮時,就感受,真是反直覺。