Java IO7:管道流、物件流
前言
前面的文章主要講了檔案字元輸入流FileWriter、檔案字元輸出流FileReader、檔案位元組輸出流FileOutputStream、檔案位元組輸入流FileInputStream,這些都是常見的流類。當然除了這些流類之外,Java還提供了很多的流類給使用者使用,本文就看一下別的流。
管道流
管道流主要用於連線兩個執行緒的通訊。管道流也分為位元組流(PipedInputStream、PipedOutputStream)和字元流(PipedReader、PipedWriter)。比如一個
下面看一下管道流的用法。既然管道流的作用是用於執行緒間的通訊,那麼勢必有傳送執行緒和接收執行緒,兩個執行緒通過管道流互動資料。首先寫一個傳送資料的執行緒:
public class Sender implements Runnable { private PipedOutputStream out = new PipedOutputStream(); public PipedOutputStream getOutputStream() { return out; } public void run() { String str = "Receiver, 你好!"; try { out.write(str.getBytes()); // 向管道流中寫入資料(傳送) out.close(); } catch (IOException e) { e.printStackTrace(); } } }
用流寫資料的時候注意關注一下,該流是否支援直接寫String,不可以的話要用String的getBytes()方法獲取字串的位元組。既然有一個傳送資料的執行緒了,接下來來一個接收資料的執行緒:
public class Receiver implements Runnable { private PipedInputStream in = new PipedInputStream(); public PipedInputStream getInputStream() { return in; } public void run() { String s = null; byte b0[] = new byte[1024]; try { int length = in.read(b0); if (-1 != length) { s = new String(b0, 0 , length); System.out.println("收到了以下資訊:" + s); } in.close(); } catch (IOException e) { e.printStackTrace(); } } }
兩個執行緒都有了,寫一個main執行緒,利用管道輸出流的connect方法連線管道輸出流和管道輸入流:
public static void main(String[] args)
{
try
{
Sender sender = new Sender();
Receiver receiver = new Receiver();
Thread senderThread = new Thread(sender);
Thread receiverThread = new Thread(receiver);
PipedOutputStream out = sender.getOutputStream(); // 寫入
PipedInputStream in = receiver.getInputStream(); // 讀出
out.connect(in);// 將輸出傳送到輸入
senderThread.start();
receiverThread.start();
}
catch (IOException e)
{
e.printStackTrace();
}
}
輸出結果應該很明顯了,大家都知道,接收執行緒接收到了來自發送執行緒通過管道流輸出流傳送的資料:
收到了以下資訊:Receiver, 你好!
注意一下,PipedInputStream運用的是一個1024位元組固定大小的迴圈緩衝區,寫入PipedOutputStream的資料實際上儲存到了對應的PipedInputStream的內部緩衝區。PipedInputStream執行讀操作時,讀取的資料實際上來自這個內部緩衝區。如果對應的PipedInputStream輸入緩衝區已滿,任何企圖寫入PipedOutputStream的執行緒都將被阻塞。而且這個寫操作執行緒將一直阻塞,直至出現讀取PipedInputStream的操作從緩衝區刪除資料。(PipedOutputStream寫入PipedInputStream的快取區中,若PipedInputStream快取區滿了,就會阻塞。)
這意味著,向PipedOutputStream寫入資料的執行緒不應該是負責從對應PipedInputStream讀取資料的唯一執行緒(所以這裡開了兩個執行緒分別用於讀寫)。假定t執行緒試圖一次對PipedOutputStream的write()方法的呼叫中向對應的PipedOutputStream寫入2000位元組的資料,在t執行緒阻塞之前,它最多能夠寫入1024位元組的資料(PipedInputStream內部緩衝區的大小)。然而,一旦t被阻塞,讀取PipedInputStream的操作就再也不能出現了,因為t是唯一讀取PipedInputStream的執行緒,這樣,t執行緒已經完全被阻塞。
物件流
Java中提供了ObjectInputStream、ObjectOutputStream這兩個類用於物件序列化操作,這兩個類是用於儲存和讀取物件的輸入輸出流類,只要把物件中的所有成員變數都儲存起來,就等於儲存了這個物件,之後從儲存的物件之中再將物件讀取進來就可以繼續使用此物件。 ObjectInputStream、ObjectOutputStream可以幫助開發者完成儲存和讀取物件成員變數取值的過程,但要求讀寫或儲存的物件必須實現了Serializable介面。
看一下例子,先來一個實現了Serializable介面的實體類Person:
public class Person implements Serializable
{
/**
* 序列化
*/
private static final long serialVersionUID = 7827863437931135333L;
private transient String name;
private int age;
private final static String sex = "man";
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
public String toString()
{
return "姓名:" + this.name + ", 年齡:" + this.age + ", 性別:" + sex;
}
}
呼叫ObjectOutputStream和ObjectInputStream寫一個序列化和反序列化的方法,我現在D盤下沒有"serializable.txt":
public static void main(String[] args) throws Exception
{
File file = new File("D:/serializable.txt");
serializable(file);
deserializable(file);
}
// 序列化物件方法
public static void serializable(File file) throws Exception
{
OutputStream outputFile = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(outputFile);
oos.writeObject(new Person("張三", 25));
oos.close();
}
// 反序列化物件方法
public static void deserializable(File file) throws Exception
{
InputStream inputFile = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(inputFile);
Person p = (Person)ois.readObject();
System.out.println(p);
}
現在執行一下,D盤下多了一個"serializable.txt",檔案裡面的內容是:
看到亂碼,因為序列化之後本身就是按照一定的二進位制格式組織的檔案,這些二進位制格式不能被文字檔案所識別,所以亂碼也是正常的。
當然,控制檯上也是有輸出的:
姓名:null, 年齡:25, 性別:man
這證明了被transient修飾的成員變數不會被序列化。