Java類庫中的IO類,可以從不同的角度將其進行不同的分類。比如按數據的流向可分分爲輸入流和輸出流;按數據處理單位的不同,又可將其分爲字節流和字符流。但不管怎樣分,其本質都是一樣的。本人更傾向將其分成字節流和字符流,再在字節流和字符流裏面討論輸入流和輸出流。
必須要知道的的是,輸入輸出是相對於“程序”來說的,從物理學方面來講就是以“程序”爲參照物:從程序中流向外部的數據就叫“輸出流”,從外部流進程序的數據就叫“輸入流”。
輸入輸出流圖
本文主要是對字節流中常見的類做個簡要的瞭解,要想對IO流中所有類有個全面認識的,建議閱讀java官方文檔。
在字節流中,所有的輸入流都直接或間接的繼承了InputStream抽象類,輸入流作爲數據的來源,它提供了read()方法用於讀取字節數據;所有的輸出流都直接或間接的繼承了OutputStream抽象類,輸出流作爲數據的去向,它也提供了write()方法用於寫入字節數據。
InputStream體系常見類
OutputStream體系常見類
正如上圖所見的那樣,IO流中大部分子類都是以基類爲後綴命名的,且都是配套存在的。下面將對這些類進行簡單的介紹。
FileInputStream & FileOutputStream
FileInputStream從文件系統中的某個文件中獲得輸入字節,可以通過File對象或文件路徑來初始化;FileOutputStream可以把數據寫入某個文件中,也是需要通過File對象或文件路徑來初始化。下面用代碼來演示這兩個類的用法。
一次處理一個字節實現文件的複製:
package com.gk.io.byte_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileStreamDemo {
public static void main(String[] args) {
copy("sourceFile.txt", "destFile.txt"); // 每次處理一個字節
}
public static void copy(String sourceFile, String destFile) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(sourceFile); // 如果指定的文件不存在則拋出FileNotFoundException
fileOutputStream = new FileOutputStream(destFile); // 如果指定的文件不存在會幫我們創建
int len = 0;
while((len = fileInputStream.read()) != -1){ // read()方法如果讀到流的末尾,則返回 -1
fileOutputStream.write(len);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}catch (IOException e) {
throw new RuntimeException(e);
}finally{
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fileOutputStream != null){
try {
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
上面演示的是一次處理一個字節的操作,雖然可以完成存取,但是速度相對較慢,所以java給我們提供了可以一次存取一個字節數組的操作,數組的大小的就是每次存取的大小,一般是1024個字節或是1024的整數倍。
上面代碼對IO流操作中出現的異常的處理是比較規範的,也是推薦使用的,但是在本文下面演示的代碼中,爲了增加可讀性,會直接把異常給拋出去。
public static void copy2(String sourceFile, String destFile) throws IOException{
FileInputStream fileInputStream = new FileInputStream(sourceFile);
FileOutputStream fileOutputStream = new FileOutputStream(destFile);
int len = 0;
byte[] bys = new byte[1024];
/*
* (len = fileInputStream.read(bys)) != -1
* 一次至少讀取bys個字節,當讀取的數據不足bys字節時,以實際讀取的爲準
* 返回值len爲實際讀取的字節長度
* 讀到流的末尾時,返回 -1
*/
while((len = fileInputStream.read(bys)) != -1){
fileOutputStream.write(bys, 0, len); // 把len個字節的數據寫入destFile中
}
fileInputStream.close();
fileOutputStream.close();
}
由於一次存取了1024個字節大小數據,所以在理論上讀取的速度將會比一次存取一個字節的情況快了1024倍。
注意,上面代碼每次運行,寫入的時候都會覆蓋原來的內容。所以FileOutputStream提供了一個可追加寫入的構造器FileOutputStream(String name, boolean append),其中append爲true時表示追加。
FilterInputStream & FilterOutputStream
單獨使用FilterInputStream這個類其實是沒多大作用的,其內部只是對InputStream的所有方法進行了簡單的重寫。在它的構造器中,需要傳入一個InputStream對象,然後再調用InputStream相應的方法。我們看一下源碼便知。
package java.io;
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
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();
}
}
可以看到,在FilterInputStream的源碼中,只是對InputStream進行了“僞實現”,並不像FileInputStream那樣對InputStream進行了實現。那麼它有什麼作用呢?其實FilterInputStream是用來提供裝飾器類接口以控制特定輸入流(InputStream)的,這是裝飾者模式的體現。它的子類有兩個重要的作用,其中DataInputStream可以讀取各種基本數據類型和String類型的數據;其他的子類使得它能夠對InputStream進行改進,即在原有的InputStream基礎上可以提供了新的功能特性。平時用的最多的就是ButtferInputStream,使得inputStream具有緩衝的功能。下面會具體介紹這兩個類。
BufferedInputStream & BufferedOutputStream
顧名思義,這兩個流都具有緩衝的作用。在BufferedInputStream內部維護有一個字節數組緩衝區buf,每次執行read操作的時候就從這buf中讀取數據,從buf中讀取數據沒有多大的開銷。如果buf中已經沒有了要讀取的數據,那麼就去執行其內部綁定的InputStream的read方法,而且是一次性讀取很大一塊數據,以便填充滿buf緩衝區。緩衝區buf的默認大小是8192字節,也就是8K,在構造器中我們也可以自己傳入一個size指定緩衝區的大小。由於我們在執行BufferedInputStream的read操作的時候,很多時候都是從緩衝區中讀取的數據,這樣就大大減少了實際執行其指定的InputStream的read操作的次數,也就提高了讀取的效率。我們來看BufferedInputStream類的read()方法源碼就知道了。
package java.io;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class BufferedInputStream extends FilterInputStream {
private static int defaultBufferSize = 8192;
protected volatile byte buf[];
private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
protected int count;
protected int pos;
protected int markpos = -1;
protected int marklimit;
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else { /* grow buffer */
int nsz = pos * 2;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
private byte[] getBufIfOpen() throws IOException {
byte[] buffer = buf;
if (buffer == null)
throw new IOException("Stream closed");
return buffer;
}
private InputStream getInIfOpen() throws IOException {
InputStream input = in;
if (input == null)
throw new IOException("Stream closed");
return input;
}
}
從上面的源碼中看到,這個類有一個buf[],也就是用來當做緩存的字節數組。每次調用read讀取數據時,先查看要讀取的數據是否在緩存中,如果在緩存中,直接從緩存中讀取;如果不在緩存中,則調用fill()方法,從InputStream中讀取一定的存儲到buf中。
與BufferedInputStream相對的是BufferedOutputStream。在BufferedOutputStream的構造器中我們需要傳入一個OutputStream,這樣就將BufferedOutputStream與該OutputStream綁定在了一起。BufferedOutputStream內部有一個字節緩衝區buf,在執行write()操作時,將要寫入的數據先一起緩存在一起,將其存入字節緩衝區buf中,buf是有限定大小的,默認的大小是8192字節,即8KB,當然也可以在構造器中傳入size指定buf的大小。該buf只要被指定了大小之後就不會自動擴容,所以其是有限定大小的,既然有限定大小,就會有被填充完的時刻,當buf被填充完畢的時候會調用BufferedOutputStream的flushBuffer方法,該方法會通過調用其綁定的OutputStream的write方法將buf中的數據進行實際的寫入操作並將buf的指向歸零(可以看做是將buf中的數據清空)。如果想讓緩存區buf中的數據理解真的被寫入OutputStream中,可以調用flush方法,flush方法內部會調用flushBuffer方法。由於buf的存在,會大大減少實際執行OutputStream的write操作的次數,優化了寫的效率。
下面是BufferedInputStream與BufferedOutputStream的代碼演示,實現了一個文件的複製功能。
package com.gk.io.byte_;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedStreamDemo {
public static void main(String[] args) throws IOException {
copy("sourceFile.txt", "destFile.txt"); // 一次處理一個字節數組
}
public static void copy(String sourceFile, String destFile)
throws IOException {
BufferedInputStream inputStream = new BufferedInputStream(
new FileInputStream(sourceFile));
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1) {
outputStream.write(bys, 0, len);
}
inputStream.close();
outputStream.close();
}
}
上面代碼將FilterInputStream包裝到BufferedInputStream中,從而使BufferedInputStream像FilterInputStream一樣也具有了讀取文件的功能,實現了文件的複製。更重要的是提高了讀取的效率。下面代碼可以測試這兩者的效率。
package com.gk.io.byte_;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class RuntimeDemo {
public static void main(String[] args) throws IOException {
int number = 100; // 記錄循環次數
long totalTime = 0; // 記錄循環number次所需總時間
for (int x = 1; x <= number; x++) {
long beginTime = System.currentTimeMillis(); // 程序開始運行時間
// 測試文件大小爲23276KB
// fun("sourceFile.mkv", "destFile.mkv"); // FileStream一次處理一個字節 --> 運行時間爲 : 259163毫秒
// fun2("sourceFile.mkv", "destFile.mkv"); // FileStream一次處理一個字節數組(1024字節) --> 運行時間爲 : 322毫秒
// fun3("sourceFile.mkv", "destFile.mkv"); // BufferedStream一次處理一個字節 --> 運行時間爲 : 361毫秒
fun4("sourceFile.mkv", "destFile.mkv"); // BufferedStream一次處理一個字節數組(1024字節) --> 運行時間爲 : 84毫秒
long endTime = System.currentTimeMillis(); // 程序結束時間
totalTime += endTime - beginTime;
System.out.println("第" + x + "次運行的時間 : " + ((endTime - beginTime)));
}
System.out.println("總時間 : " + totalTime);
System.out.println("平均每次運行時間爲 : " + (totalTime / number) + "毫秒"); // 誤差允許的範圍內不考慮除法的精度問題
}
public static void fun(String sourceFile, String destFile)
throws IOException {
FileInputStream inputStream = new FileInputStream(sourceFile);
FileOutputStream outputStream = new FileOutputStream(destFile);
int len = 0;
while ((len = inputStream.read()) != -1) {
outputStream.write(len);
}
// 釋放資源
close(inputStream,outputStream);
}
public static void fun2(String sourceFile, String destFile)
throws IOException {
FileInputStream inputStream = new FileInputStream(sourceFile);
FileOutputStream outputStream = new FileOutputStream(destFile);
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1) {
outputStream.write(bys, 0, len);
}
// 釋放資源
close(inputStream,outputStream);
}
public static void fun3(String sourceFile, String destFile)
throws IOException {
BufferedInputStream inputStream = new BufferedInputStream(
new FileInputStream(sourceFile));
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
while ((len = inputStream.read()) != -1) {
outputStream.write(len);
}
// 釋放資源
close(inputStream,outputStream);
}
public static void fun4(String sourceFile, String destFile)
throws IOException {
BufferedInputStream inputStream = new BufferedInputStream(
new FileInputStream(sourceFile));
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1) {
outputStream.write(bys, 0, len);
}
// 釋放資源
close(inputStream,outputStream);
}
private static void close(InputStream inputStream, OutputStream outputStream) throws IOException{
if(inputStream != null){
inputStream.close();
}
if(outputStream != null){
outputStream.close();
}
}
}
我測試的文件是一個以.mkv爲後綴的音頻文件,大小爲23276KB(儘量用大一點的文件測試才能更好的看出效果),在我的機器用FileInputStream一次讀取一個字節完成複製所需時間是259163毫秒,一次讀取一個字節數組(1024字節)所需時間是322毫秒;但是用BufferedInputStream處理卻大大縮小了複製的時間,一次讀取一個字節的時間是361毫秒,一次讀取一個字節數組(1024字節)的時間卻只要短短的84毫秒。從對比中我們就可以看出使用BufferedInputStream的好處了。
DataInputStream & DataOutputStream
前面操作的都是字節數據,但有時候我們想操作的是java基本類型的數據,這時候就可以通過DataInputStream的readXXX()方法讀取java基本類型的數據和String類型的數據了,這是一些非常有用的方法。如果我們既想要讀取基本類型的數據又想提高讀取效率的話,我們可以這樣使用。
package com.gk.io.byte_;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
write("destFile.txt");
read("destFile.txt");
}
public static void write(String destFile) throws IOException {
DataOutputStream outputStream =
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(destFile)));
outputStream.writeBoolean(true);
outputStream.writeByte(47);
outputStream.writeShort(147);
outputStream.writeChar('j');
outputStream.writeInt(520);
outputStream.writeLong(10810810888L);
outputStream.writeFloat(3.14F);
outputStream.writeDouble(2.718281828D);
outputStream.writeUTF("I love java");
// 釋放資源
outputStream.close();
}
public static void read(String destFile) throws IOException {
DataInputStream inputStream =
new DataInputStream(
new BufferedInputStream(
new FileInputStream(destFile)));
System.out.println("boolean : " + inputStream.readBoolean());
System.out.println("byte : " + inputStream.readByte());
System.out.println("short : " + inputStream.readShort());
System.out.println("char : " + inputStream.readChar());
System.out.println("int : " + inputStream.readInt());
System.out.println("long : " + inputStream.readLong());
System.out.println("float : " + inputStream.readFloat());
System.out.println("double : " + inputStream.readDouble());
System.out.println("String : " + inputStream.readUTF());
inputStream.close();
}
}
通過將BufferedInputStream包裝到DataInputStream中,使DataInputStream具有BufferedInputStream高效的功能。不過要注意讀取與寫入的順序要一致。不然會出錯。
PrintStream
PrintStream也是FilterOutputStream的直接子類,它爲其他輸出流添加了功能,使它們能夠方便地打印各種數據值表示形式。提供了print、println等方法方便我們操作,println實現了自動換行。除此之外,構造器PrintStream(OutputStream out, boolean autoFlush)還能實現自動刷新,當autoFlush爲true時。
package com.gk.io.byte_;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class PrintStreamDemo {
public static void main(String[] args) throws FileNotFoundException {
write("printStream.txt");
}
public static void write(String fileName) throws FileNotFoundException {
PrintStream printStream = new PrintStream(fileName);
printStream.println(47); // int
printStream.print(13.14); // double
printStream.println("I love java"); // string
printStream.println(true); // boolean
// 釋放資源
printStream.close();
}
}
打印”13.14”的時候使用了print所以沒有換行效果。
ByteArrayInputStream & ByteArrayOutputStream
雖然IO流中大部分操作都是針對文件進行的,但有時我們只想把數據保存在內存中,程序結束的時候數據就從內存中消失,這樣既能提高程序的運行效率又不會佔用太多的存儲空間,ByteArrayInputStream和ByteArrayOutputStream就是用來幫助我們操作內存的。
ByteArrayOutputStream其內部有一個字節數組用於存儲write()操作時寫入的數據,在構造器中可以傳入一個size指定其內部的byte數組的大小,如果不指定,那麼默認它會將byte數組初始化爲32字節,當持續通過write向ByteArrayOutputStream中寫入數據時,如果其內部的byte數組的剩餘空間不能夠存儲需要寫入的數據,那麼那麼它會通過調用內部的ensureCapacity(int minCapacity)方法對其內部維護的byte數組進行擴容以存儲所有要寫入的數據,所以不必擔心其內部的byte數組太小導致的IndexOutOfBoundsException之類的異常。
ByteArrayInputStream構造器中需要傳入一個byte數組作爲數據源,當執行read操作時,就會從該數組中讀取數據。
package com.gk.io.byte_;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayStreamDemo {
public static void main(String[] args) throws IOException {
byte[] bys = write();
read(bys);
}
public static byte[] write() throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write("I love java".getBytes());
outputStream.close(); // 關閉此流無效
// 關閉流之後再對其進行操作不會報IOException
outputStream.write("\r\n".getBytes()); // Windows下的回車換行
outputStream.write("I love python too".getBytes());
return outputStream.toByteArray();
}
public static void read(byte[] bys) throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(bys);
int by = 0;
while((by = inputStream.read()) != -1){
System.out.print((char)by);
}
inputStream.close(); // 關閉此流無效
System.out.println();
System.out.println(inputStream.read()); // 關閉流之後再對其進行操作不會報IOException
}
}
上面代碼演示了將一個字符數組寫進一個ByteArrayOutputStream中,然後再用toByteArray()將其返回傳遞給ByteArrayInputStream的構造器,最後ByteArrayInputStream的read()就可以讀取數據了。
"\r\n"是Windows系統下的回車換行,也就是說上面的程序使用的時候受到了限制,不能實現平臺無關性。在BufferedWriter類中將提供newLine()方法,此方法實現了無關乎平臺的換行。
與其他流不同,關閉這兩個流是無效的,也就是說關閉之後我們依然可以對其進行操作而不會報IOException。查看源碼便知,close()是空現實的。
package java.io;
import java.util.Arrays;
public class ByteArrayOutputStream extends OutputStream {
// ...省略其他源碼
public void close() throws IOException {
}
}
ObjectInputStream & ObjectOutputStream
ObjectInputStream和ObjectOutputStream被稱爲序列化流。和DataOutputStream一樣ObjectOutputStream也實現了DataOutput接口,所以也具有一系列的writeXXX方法,可以很方便的向指定的輸出流中寫入基本類型數據比如writeBoolean、writeChar、writeInt、writeFloat、writeUTF、writeObject等。不過一般用它來操作對象(writeObject()),需要注意的是writeObject()方法中寫入的類型必須要實現Serializable接口,從而在執行writeObject()操作時將對象進行序列化成流,並將其寫入指定的輸出流中;ObjectInputStream是與ObjectOutputStream對應的輸入類,具有一系列的readXXX方法(實現了DataInput接口),專門用於讀取OutputStream通過writeXXX寫入的數據。
實現Serializable接口的Student類
package com.gk.io.bean;
import java.io.Serializable;
public class Student implements Serializable{
/**
*
*/
private static final long serialVersionUID = -8083372836565374164L;
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
對象的存取
package com.gk.io.byte_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.gk.io.bean.Student;
public class ObjectStreamDemo {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//write("destFile.txt");
read("destFile.txt");
}
public static void write(String destFile) throws FileNotFoundException, IOException {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(destFile));
// 寫入對象,如果對象沒有實現Serializable接口,則會拋出NotSerializableException
outputStream.writeObject(new Student("zhangSan", 33));
outputStream.writeObject(new Student("liSi", 44));
outputStream.writeObject(new Student("wangWu", 55));
outputStream.close(); // 釋放資源
}
public static void read(String destFile) throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(destFile));
// 讀取對象
System.out.println((Student)inputStream.readObject());
System.out.println((Student)inputStream.readObject());
System.out.println((Student)inputStream.readObject());
inputStream.close(); // 釋放資源
}
}
在使用ObjectOutputStream時,如果所操作的對象沒有實現序列化接口(Serializable)就會拋出NotSerializableException。注意在Student類中有一個序列版本號常量serialVersionUID,它的作用是當我們使用ObjectOutputStream寫入對象後,在用ObjectInputStream讀取對象之前,允許我們對所操作的對象的字段進行修改而不會有InvalidClassException。讀者可以試試在實現序列化接口的實體類中沒有添加serialVersionUID的情況下,先用ObjectOutputStream的writeObject()方法寫入對象,再修改對象的屬性(刪除或是修改其修飾符),然後用ObjectInputStream的readObject()讀此對象,看看有什麼不同。
SequenceInputStream
這個流比較特殊,它不像前面幾個流那樣有對應的OutputStream。它的作用是將兩個或多個InputStream在邏輯上合併爲一個InputStream。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到到達文件末尾,接着從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的文件末尾爲止。在我們理解了SequenceInputStream的作用是將多個輸入流合併爲一個輸入流之後,我們就能理解爲什麼不存在對應的SequenceOutputStream類了,因爲將一個輸出流拆分爲多個輸出流是沒有意義的。
package com.gk.io.byte_;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.Vector;
public class SequenceStreamDemo {
public static void main(String[] args) throws IOException {
copy("file1.txt", "file2.txt", "destFile.txt"); // 合併兩個流
//copy2("file1.txt", "file2.txt", "file3.txt", "destFile.txt"); // 合併多個流
}
public static void copy(String file1, String file2, String destFile) throws IOException {
@SuppressWarnings("resource")
SequenceInputStream inputStream = new SequenceInputStream(
new FileInputStream(file1),
new FileInputStream(file2));
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1){
outputStream.write(bys, 0, len);
}
inputStream.close();
outputStream.close();
}
public static void copy2(String file1, String file2, String file3,String destFile) throws IOException {
Vector<InputStream> streams = new Vector<InputStream>();
streams.add(new FileInputStream(file1));
streams.add(new FileInputStream(file2));
streams.add(new FileInputStream(file3));
/*
* 合併多個流的構造器
* public SequenceInputStream(Enumeration<? extends InputStream> e)
*/
SequenceInputStream inputStream = new SequenceInputStream(streams.elements());
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(destFile));
int len = 0;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1){
outputStream.write(bys, 0, len);
}
inputStream.close();
outputStream.close();
}
}
上面代碼分別實現了將兩個和多個文件的內容複製到一個文件中。雖然可以用FileOutputStream(File file, boolean append)以追加的形式實現,但顯然用SequenceInputStream更加便捷。值得注意的是當需要合併多個流的時候SequenceInputStream的構造器需要一個Enumeration接口,所以我們可以將多個流對象放在Vector中,再調用Vector的elements()方法返回Enumeration。
參考資料:
《java編程思想 第4版》
http://blog.csdn.net/iispring/article/details/46821033