Java 的輸入輸出總是給人一種很混亂的感覺,要想把這個問題搞清楚,必須對各種與輸入輸出相關的類之間的關係有所瞭解。只有你瞭解了他們之間的關係,知道設計這個類的目的是什麼,才能更從容的使用他們。
字節流輸出
圖1 Java 字節輸出類
- OutputStream
OutputStream是所有字節輸出類的超類,這是個抽象類,需要實現其中定義的 write
函數,纔能有實用的功能。
public abstract void write(int b) throws IOException;
其它方法都是在 write
的基礎上實現的。例如這個多態的 write
:
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
FileOutputStream
會將內容輸出到 File
或者 FileDescriptor
,
此類是按照字節輸出,如果想按照字符輸出,可以使用FileReader
類。
構造器中,需要指明輸出的文件:
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
函數,與操作系統相關。
private native void write(int b, boolean append) throws IOException;
如果對比一下字節輸入類,你會發現輸入和輸出在實現上有很大的相似性,它們是對稱的。
- ByteArrayOutputStream
ByteArrayOutputStream 會將數據寫入字節數組中, 可以通過 toByteArray,toString
得到這些數據。
protected byte buf[];
初始化時,可以指定這個數組的大小:
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
}
寫入時,會寫入這個數組。write
會先保證數組的大小,如果不夠用,還會自動進行擴充。
public synchronized void write(int b) {
ensureCapacity(count + 1);
buf[count] = (byte) b;
count += 1;
}
- FilterOutputStream
所有有過濾功能的類的基類,例如,對輸出流進行轉化,或者添加新的功能。初始化時,需要提供一個底層的流,用於寫入數據,FilterOUtputStream
類的所有方法都是通過調用這個底層流的方法實現的。
初始化時,
protected OutputStream out;
public FilterOutputStream(OutputStream out) {
this.out = out;
}
寫入時:
public void write(int b) throws IOException {
out.write(b);
}
- BufferedOutputStream
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
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));
}
- PipedOutputStream
管道輸出流可以與一個管道輸入流相關聯,關聯後,共用一個緩衝區,輸出流寫入數據,輸入流讀取數據,二者應該處於不同線程,否則可能出現死鎖。
原理上一篇文章在介紹 PipedInputStream 時,已經闡述。
另外,我覺得在這裏,有必要說一下那幾個用於壓縮和解壓縮的類,實現就不說了,就講下他們的功能與關係。
JAVA IO 壓縮與解壓縮
- InflaterInputStream: 用於解壓 deflate 格式的壓縮數據,底層流爲壓縮後的數據,read 返回解壓後的數據。
- InflaterOutputStream: 用於解壓 deflate 格式的壓縮數據,底層流爲壓縮後的數據,write 寫入解壓後的數據。
- DeflaterInputStream: 用於壓縮成 deflate 格式的數據,底層流爲未壓縮數據,read 返回壓縮後的數據。
-
DeflaterOutputStream: 用於壓縮成 deflate 格式的數據,底層流爲未壓縮數據,write 寫入壓縮後的數據。
-
GZIPInputStream: 用於解壓 GZip 格式的壓縮數據,底層流爲壓縮後的數據,read 返回解壓後的數據。它是 InflaterInputStream 的子類。
- GZIPOutputStream: 用於壓縮成 Gzip格式的數據,底層流爲未壓縮數據,write 寫入壓縮後的數據。是 DeflaterOutputStream 的子類(注意不是InflaterOutputStream) 。
不得不說,這個API設計的真是太反直覺了。GZIP 格式的解壓和壓縮一個是 GZIPInputStream,一個是 GZIPOutputStream。而 deflate 格式的解壓和壓縮,一個是 InflaterInputStream/InflaterOutputStream,另一個是 DeflaterInputStream/DeflaterOutputStream。當同時需要對 gzip 和 deflate 壓縮和解壓縮時,就感覺,真是反直覺。