對象的序列化和反序列化
序列化 (Serialization)將對象的狀態(tài)信息轉換為可以存儲或傳輸?shù)男问降倪^程浮入。在序列化期間塑悼,對象將其當前狀態(tài)寫入到臨時或持久性存儲區(qū)讥蔽。以后疏咐,可以通過從存儲區(qū)中讀取或反序列化對象的狀態(tài)供炼,重新創(chuàng)建該對象兼蕊。
當你創(chuàng)建對象時集侯,只要你需要养筒,它就會一直存在砌函,但是當程序終止的時候斩披,那么這個對象也就隨之消失了,盡管這么做是有意義的,但是仍舊存在某些的情況雏掠,如果對象能夠在程序不運行的情況下仍能存在并且保存其信息斩祭,那將會是非常有用的。這樣在下次運行程序的同事乡话,該對象能夠被重建并且擁有的信息與程序上次運行時它所擁有的信息相同摧玫。
簡單來說序列化和反序列化如下
- 序列化:把對象轉換為字節(jié)序列的過程稱為對象的序列化
- 反序列化:把字節(jié)序列恢復為對象的過程稱為對象的反序列化
而什么時候會用到序列化呢?一般在以下的情況中會使用到序列化
- 對象的持久化:把對象的字節(jié)序列永久地保存到硬盤上绑青,通常存放在一個文件中
在很多應用中诬像,需要對某些對象進行序列化,讓它們離開內存空間闸婴,入住物理硬盤坏挠,以便長期保存。比如最常見的是Web服務器中的Session對象邪乍,當有 10萬用戶并發(fā)訪問降狠,就有可能出現(xiàn)10萬個Session對象,內存可能吃不消庇楞,于是Web容器就會把一些seesion先序列化到硬盤中榜配,等要用了,再把保存在硬盤中的對象還原到內存中吕晌。
- 遠程調用:在網(wǎng)絡上傳送對象的字節(jié)序列
當兩個進程在進行遠程通信時蛋褥,彼此可以發(fā)送各種類型的數(shù)據(jù)。無論是何種類型的數(shù)據(jù)睛驳,都會以二進制序列的形式在網(wǎng)絡上傳送烙心。發(fā)送方需要把這個Java對象轉換為字節(jié)序列,才能在網(wǎng)絡上傳送乏沸;接收方則需要把字節(jié)序列再恢復為Java對象淫茵。
序列化的基本實現(xiàn)
只要對象實現(xiàn)了Serializable
接口,對象的序列化就會變得十分簡單屎蜓。要序列化一個對象首先要創(chuàng)建某些OutputStream
對象痘昌,然后將其封裝在一個ObejctOutputStream
對象內,這時只需要調用writeObject()
即可將對象序列化炬转,并將其發(fā)送給OutputStream
辆苔。
對象序列化是基于字節(jié)的,所以要使用
InputStream
和OutputStream
繼承層次結構
如果要反向上面的過程(即將一個序列還原為一個對象)扼劈,需要將一個InputStream
封裝在ObjectInputStream
內驻啤,然后調用readObject()
,和往常一樣荐吵,我們最后獲得是一個引用骑冗,它指向了一個向上轉型的Object赊瞬,所以必須向下轉型才能直接設置它們。
對象序列化不僅能夠將實現(xiàn)了接口的那個類進行序列化贼涩,也能夠將其引用的對象也實例化巧涧,以此類推。這種情況可以被稱之為對象網(wǎng)
遥倦。單個對象可與之建立連接谤绳。
下面我們舉個例子可以看到在序列化和反序列過程中,對象網(wǎng)中的連接的對象信息都沒有變袒哥。
public class TestSerializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String fileName = "/Users/hupengfei/mytest.sql";
Worm w = new Worm(6,'a');
System.out.println("w:"+w);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName));
out.writeObject("Worm Storage\n");
out.writeObject(w);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName));
String s = (String) in.readObject();
Worm w2 = (Worm) in.readObject();
System.out.println(s+"w2:"+w2);
}
}
class Data implements Serializable{
private Integer i ;
public Data(Integer i ){
this.i = i;
}
@Override
public String toString() {
return i.toString();
}
}
class Worm implements Serializable{
private static final long serialVersionUID = 8033549288339500180L;
private static Random random = new Random(47);
private Data [] d = {
new Data(random.nextInt(10)),
new Data(random.nextInt(10)),
new Data(random.nextInt(10))
};
private Worm next;
private char c;
public Worm(int i ,char x){
System.out.println("Worm Constructor:"+i);
c = x;
if (--i>0){
next = new Worm(i,(char)(x+1));
}
}
public Worm(){
System.out.println("Default Constructor");
}
@Override
public String toString() {
StringBuffer result = new StringBuffer(":");
result.append(c);
result.append("(");
for (Data data: d){
result.append(data);
}
result.append(")");
if (next!=null){
result.append(next);
}
return result.toString();
}
}
可以看到打印信息如下
Worm Constructor:6
Worm Constructor:5
Worm Constructor:4
Worm Constructor:3
Worm Constructor:2
Worm Constructor:1
w::a(853):b(119):c(802):d(788):e(199):f(881)
Worm Storage
w2::a(853):b(119):c(802):d(788):e(199):f(881)
在生成Data對象時是用隨機數(shù)初始化的缩筛,從輸出中可以看出,被還原后的對象確實包含了原對象中的所有鏈接堡称。
上面我們舉了個如何進行序列化的例子瞎抛,其中或許看到了serialVersionUID
這個字段,如果不加的話却紧,那么系統(tǒng)會自動的生成一個桐臊,而如果修改了類的話,哪怕加一個空格那么這個serialVersionUID
也會改變啄寡,那么在反序列化的時候就會報錯豪硅,因為在反序列化的時候會將serialVersionUID
和之前的serialVersionUID
進行對比,只有相同的時候才會反序列化成功挺物。所以還是建議顯視的定義一個serialVersionUID
。
transient
(瞬時)關鍵字
當我們在對序列化進行控制的時候飘弧,可能需要某個字段不想讓Java進行序列化機制進行保存其信息與恢復识藤。如果一個對象的字段保存了我們不希望將其序列化的敏感信息(例如密碼)。盡管我們使用private
關鍵字但是如果經過序列化次伶,那么在進行反序列化的時候也是能將信息給恢復過來的痴昧。我們舉個例子如下:
我們定義個Student
類
class Student implements Serializable{
private static final long serialVersionUID = 1734284264262085307L;
private String password;
------get set 方法
}
然后將其序列化到文件中然后再從文件中反序列化
public static void main(String[] args) throws IOException, ClassNotFoundException {
String fileName="/Users/hupengfei/mytest.sql";
Student student = new Student();
student.setPassword("123456");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(fileName));
objectOutputStream.writeObject(student);
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileName));
Student readStudent = (Student) objectInputStream.readObject();
System.out.println(readStudent.getPassword());
}
然后發(fā)現(xiàn)輸出為
readStudent的password=123456
此時我們如果想password
參數(shù)在序列化的時候存儲其值,那么可以加上transient
關鍵字冠王,就像下面一樣
private transient String password;
然后輸出如下
readStudent的password=null
發(fā)現(xiàn)在序列化的時候參數(shù)就已經沒被保存進去了