java -- IO流之字節流

 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),其中appendtrue時表示追加。


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中已經沒有了要讀取的數據,那麼就去執行其內部綁定的InputStreamread方法,而且是一次性讀取很大一塊數據,以便填充滿buf緩衝區。緩衝區buf的默認大小是8192字節,也就是8K,在構造器中我們也可以自己傳入一個size指定緩衝區的大小。由於我們在執行BufferedInputStreamread操作的時候,很多時候都是從緩衝區中讀取的數據,這樣就大大減少了實際執行其指定的InputStreamread操作的次數,也就提高了讀取的效率。我們來看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被填充完畢的時候會調用BufferedOutputStreamflushBuffer方法,該方法會通過調用其綁定的OutputStreamwrite方法將buf中的數據進行實際的寫入操作並將buf的指向歸零(可以看做是將buf中的數據清空)。如果想讓緩存區buf中的數據理解真的被寫入OutputStream中,可以調用flush方法,flush方法內部會調用flushBuffer方法。由於buf的存在,會大大減少實際執行OutputStreamwrite操作的次數,優化了寫的效率。 

 

 下面是BufferedInputStreamBufferedOutputStream的代碼演示,實現了一個文件的複製功能。


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中,從而使BufferedInputStreamFilterInputStream一樣也具有了讀取文件的功能,實現了文件的複製。更重要的是提高了讀取的效率。下面代碼可以測試這兩者的效率。


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基本類型的數據,這時候就可以通過DataInputStreamreadXXX()方法讀取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的直接子類,它爲其他輸出流添加了功能,使它們能夠方便地打印各種數據值表示形式。提供了printprintln等方法方便我們操作,println實現了自動換行。除此之外,構造器PrintStream(OutputStream out, boolean autoFlush)還能實現自動刷新,當autoFlushtrue時。


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流中大部分操作都是針對文件進行的,但有時我們只想把數據保存在內存中,程序結束的時候數據就從內存中消失,這樣既能提高程序的運行效率又不會佔用太多的存儲空間,ByteArrayInputStreamByteArrayOutputStream就是用來幫助我們操作內存的。

 

 ByteArrayOutputStream其內部有一個字節數組用於存儲write()操作時寫入的數據,在構造器中可以傳入一個size指定其內部的byte數組的大小,如果不指定,那麼默認它會將byte數組初始化爲32字節,當持續通過writeByteArrayOutputStream中寫入數據時,如果其內部的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的構造器,最後ByteArrayInputStreamread()就可以讀取數據了。

 

 "\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


 ObjectInputStreamObjectOutputStream被稱爲序列化流。和DataOutputStream一樣ObjectOutputStream也實現了DataOutput接口,所以也具有一系列的writeXXX方法,可以很方便的向指定的輸出流中寫入基本類型數據比如writeBooleanwriteCharwriteIntwriteFloatwriteUTFwriteObject等。不過一般用它來操作對象(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的情況下,先用ObjectOutputStreamwriteObject()方法寫入對象,再修改對象的屬性(刪除或是修改其修飾符),然後用ObjectInputStreamreadObject()讀此對象,看看有什麼不同。


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中,再調用Vectorelements()方法返回Enumeration




參考資料:

《java編程思想 第4版》

http://blog.csdn.net/iispring/article/details/46821033

http://blog.csdn.net/zhao123h/article/details/52826682


































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章