Head First Java學(xué)習(xí):第十四章-序列化和文件
第十四章 序列化和文件的輸入輸出
保存對象
1、什么是序列化和反序列化
在編程的世界當(dāng)中,常常有這樣的需求:我們需要將本地已經(jīng)實例化的某個對象,通過網(wǎng)絡(luò)傳遞到其他機器當(dāng)中,為了滿足這種需求,就有了所謂的序列化和反序列化。
序列化就是,把內(nèi)存中的某個對象壓縮成字節(jié)流的形式;
反序列化就是,把字節(jié)流轉(zhuǎn)換成內(nèi)存中的對象。
2、把序列化對象寫入文件
四部曲:
// 創(chuàng)建出 FileOutputStream
FileOutputStream fs = new FileOutputStream("foo.ser");
// 創(chuàng)建ObjectOutputStream
ObjectOutputStream os = new ObjectOutputStream(fs);
// 寫入對象
os.writeObject(myBox);
// 關(guān)閉ObjectOutputStream
os.close();
說明:

stream 串流,把stream連接起來代表源和目的地的連接,即文件和網(wǎng)絡(luò)端口的連接。串流 必須要連接到某處才能算是個串流。
串流一般要倆倆連接才能做出有意義的事情,一個表示連接,一個要被調(diào)用方法。
按照單一設(shè)計原則,每個類做一件事情。
FileOutputStream 把字節(jié)寫入文件。
ObjectOutputStream 把對象轉(zhuǎn)換成可以寫入串流的數(shù)據(jù)。
調(diào)用ObjectOutputStream的writeObject時,對象會被打成stream 送到FileOutputStream,來寫入文件。
3、對象被序列化發(fā)生了什么?
在堆上的對象,有狀態(tài)即實例變量的值。這些值讓同一類的不同實例有不同意義。
被序列化的對象,保存了實例變量的值因此之后可以在堆上帶回一模一樣的實例。
實例變量的值和java虛擬機所需要的信息會被保存到文件中,一般是字節(jié)碼或者xml編碼格式文件。
對象的狀態(tài)時什么,有什么需要保存?
對象的狀態(tài) –—— 實例變量的值
- 如果是 primitive 主數(shù)據(jù)類型,直接保存
- 如果是引用其他對象,那所有對象都被保存。
當(dāng)對象被序列化,該對象引用的實例變量也會被序列化。且所有被引用的對象也會被序列化。這些操作都是自動進(jìn)行的。
4、類要被序列化,就要實現(xiàn) Serializable
objectOutputStream.writeObject(myBox);
myBox必須要實現(xiàn)序列化,否則執(zhí)行會出問題。
如果序列化的類中,實例變量引用了非序列化的類,執(zhí)行會報錯。
Serializable接口沒有任何方法需要實現(xiàn),唯一目的是申明有實現(xiàn)它的類是可以被序列化的。某個類被序列化,其子類自動可以序列化。
舉例:
package chap14;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Box implements Serializable {
private int width;
private int heigh;
public void setWidth(int w){
width = w;
}
public void setHeigh(int h){
heigh = h;
}
public static void main(String[] args) {
Box myBox = new Box();
myBox.setWidth(6);
myBox.setHeigh(7);
try{
// 創(chuàng)建出 FileOutputStream
FileOutputStream fs = new FileOutputStream("foo.ser");
// 創(chuàng)建ObjectOutputStream
ObjectOutputStream os = new ObjectOutputStream(fs);
// 寫入對象
os.writeObject(myBox);
// 關(guān)閉ObjectOutputStream
os.close();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
5、實例變量不想被序列化,標(biāo)記為 transient 。
實例變量被標(biāo)記為 transient ,序列化會跳過;
反序列化會設(shè)置為數(shù)據(jù)類型初始值,如primitive類型是0/0.0/false,引用類型是null。
6、反序列化步驟
舉例:
// 1 創(chuàng)建FileInputStream
FileInputStream fileStream = new FileInputStream("foo.ser");
// 2 創(chuàng)建ObjectInputStream
ObjectInputStream os = new ObjectInputStream(fileStream);
// 3 讀取對象
Object one = os.readObject();
Object two = os.readObject();
// 4 轉(zhuǎn)換對象類型
GameCharacter elf = (GameCharacter) one;
GameCharacter troll = (GameCharacter) two;
// 5 關(guān)閉ObjectInputStream
os.close();
說明:

- 對象從stream中讀出來
- java虛擬機通過存儲的信息判斷出對象的class類型
- java虛擬機嘗試尋找和加載對象的類,如果虛擬機找不到或無法加載該類,則java虛擬機會拋出異常
- 新的對象會被分配帶堆上,但是構(gòu)造函數(shù)不會執(zhí)行。因為執(zhí)行構(gòu)造函數(shù),會把對象的狀態(tài)抹去又變成全新,而這不是我們想要的結(jié)果。我們需要的是對象回到存儲時的狀態(tài)。
- 如果對象在繼承樹上有個不可序列化的祖先類,則該不可序列化類以及在它之上的類的構(gòu)造函數(shù)(就算是可序列化)都會執(zhí)行。一旦構(gòu)造函數(shù)連鎖啟動之后將無法停止。也就是說,從第一個不可序列化的父類開始,全部都會重新初始狀態(tài)。
- 對象的實例變量會被還原成序列化時點的狀態(tài)值。transient變量會被賦值null的對象引用,primitive主數(shù)據(jù)類型的默認(rèn)為0/0.0/false等值。
7、FileWriter 將字符串寫入文本文件
用FileWritrer替代FileOutputStream,但是不會把它連接到 ObjectOutputStream上。
FileWriter b = new FileWriter(“FOO.txt”);
b.write(“hello foo@”);
b.close();
8、io.File class
File 對象代表磁盤上的文件或目錄的路徑名稱,不代表讀取或代表文件中的數(shù)據(jù)
如:/Users/Kathy/Data/GameFile.txt
File對象可以做的事情
- 創(chuàng)建出代表現(xiàn)存盤文件的File對象
舉例:
File f = new File(“mycode.txt”);
- 建立新的目錄
舉例:
File dir = new File(“Chapter7”);
dir.mkdir();
- 列出目錄下的內(nèi)容
舉例:
if(dir.isDirectoy){
String[] dirContents = dir.list();
for(int i=0;i<dirContents.length;i++){
sout(dirContents[i])
}
}
- 取得文件或目錄的絕對路徑
舉例:
sout(dir.getAbsolutePath());
- 刪除文件或目錄(成功會返回true)
Boolean isDeleted = f.delete();
9、緩沖區(qū)
緩沖區(qū)的好處是同時把多個字符串寫入文件,提高效率,節(jié)省了磁盤操作的時間。

舉例:
BufferedWriter writer = new BufferedWriter(new FileWriter(aFile));
強制刷新緩沖區(qū)數(shù)據(jù):writer.flush()
10、FileReader 讀取文本文件
以File對象表示文件,以FileReader來執(zhí)行實際的讀取,并用BufferedReader 來讓讀取效率更高。
讀取以while 循環(huán)來逐行進(jìn)行,一直到readLine()的結(jié)果為null 為止。
舉例:
package chap14;
import java.io.*;
public class ReadFile {
public static void main(String[] args) {
try{
// File 對象
File myFile = new File("/Users/huqiqi/Desktop/JavaStudy/JavaStudyCode/HeadFirstJavaMaster/src/main/java/chap14/MyText.txt");
// FileReader 是字符的連接到文本文件的串流
FileReader fileReader = new FileReader(myFile);
// 將FileReader 鏈接到BufferedReader 以獲取更高的效率。
// 它只會在緩沖區(qū)讀空的時候,才會回頭區(qū)磁盤讀取
BufferedReader reader = new BufferedReader(fileReader);
// 承接所讀取的結(jié)果
String line = null;
// 讀一行列一行直到?jīng)]有東西可讀取
while ((line = reader.readLine())!=null){
System.out.println(line);
}
reader.close();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
結(jié)果:
hello
this is a file
test FileReader
11、String的split()
舉例:
String toTest = "what is blue + yellow?/green?";
String[] result = toTest.split("/");
for(String token:result){
System.out.println(token);
}
結(jié)果:
what is blue + yellow?
green?
說明:String的split() 可以把字符串按照指定參數(shù)拆成兩部分。
12、VersionID:序列化的識別
目的:做版本控制。
13、使用serialVersionUID
序列化是將對象的狀態(tài)信息轉(zhuǎn)換成可存儲或傳輸?shù)男问降倪^程。Java的對象保存在JVM的堆內(nèi)存中,如果JVM堆不存在了,那么對象也就跟著消失了。
序列化提供了一種方案,可以讓你在即使JVM停機的情況下也能把對象保存下來的方案。把Java對象序列化成可存儲或傳輸?shù)男问剑ㄈ缍M(jìn)制流),比如保存到文件中。這樣,當(dāng)再次需要這個對象的時候,從文件中讀取出二進(jìn)制流,再從二進(jìn)制流中反序列化對象。
虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點就是 兩個類的序列化ID是否一致,這個所謂的序列化ID,就是代碼中定義的serialVersionUID。
浙公網(wǎng)安備 33010602011771號