原帖地址:原帖
個人網(wǎng)站地址:個人網(wǎng)站
簡書對markdown的支持太完美了袄膏,我竟然可以直接Ctrl C/V過來。
定義
Java序列化是指把Java對象轉(zhuǎn)換為字節(jié)序列的過程哎媚;
Java反序列化是指把字節(jié)序列恢復(fù)為Java對象的過程忽你。
應(yīng)用場景
當(dāng)兩個進(jìn)程進(jìn)行遠(yuǎn)程通信時,可以相互發(fā)送各種類型的數(shù)據(jù)颖系,包括文本、圖片辩越、音頻嘁扼、視頻等, 而這些數(shù)據(jù)都會以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送黔攒。那么當(dāng)兩個Java進(jìn)程進(jìn)行通信時趁啸,如何實(shí)現(xiàn)進(jìn)程間的對象傳送呢?這就需要Java序列化與反序列化了督惰。一方面不傅,發(fā)送方需要把這個Java對象轉(zhuǎn)換為字節(jié)序列,然后在網(wǎng)絡(luò)上傳送赏胚;另一方面访娶,接收方需要從字節(jié)序列中恢復(fù)出Java對象。數(shù)據(jù)傳輸便是序列化與反序列化的主要應(yīng)用場景之一觉阅。當(dāng)然也可用于數(shù)據(jù)的存儲與讀取震肮。
實(shí)現(xiàn)方式
java的序列化有兩種方式:
- 實(shí)現(xiàn)序列化接口Serializable,這個使用的比較多留拾。Serializable接口是一個空的接口戳晌,它的主要作用就是標(biāo)識這個類的對象是可序列化的。
- 實(shí)現(xiàn)接口Externalizable痴柔。Exterinable繼承了Serializable沦偎,是Serializable的一個擴(kuò)展,對于哪些屬性可以序列化咳蔚,哪些可以反序列化可以做詳細(xì)地約束豪嚎。
相關(guān)工具類:
-
java.io.ObjectOutputStream:表示對象輸出流
它是OutputStream類的一個子類,對應(yīng)的ObjectOutputStream.WriteObject(Object object)就要求參數(shù)object實(shí)現(xiàn)Serializable接口谈火。
使用方式如下:
import java.io.FileOutputStream; import java.io.ObjectOutputStream; String fileName = "test.txt"; //文件名 LoginInfo info = new LoginInfo("chen","123"); //某個待序列化對象侈询,LoginInfo類須實(shí)現(xiàn)Serializable接口 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName)); //創(chuàng)建輸出流 oos.writeObject(info); //序列化 oos.close(); //關(guān)閉流
-
java.io.ObjectInputStream:表示對象輸入流
相應(yīng)地,它的readObject(Object object)方法從輸入流中讀取字節(jié)序列糯耍,再把它們反序列化成為一個對象扔字,并返回囊嘉。
使用方式如下:
import java.io.FileInputStream; import java.io.ObjectInputStream; String fileName = "test.txt"; ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName)); LoginInfo info2 = (LoginInfo)ois.readObject(); ois.close();
方式一:實(shí)現(xiàn)Serializable接口
Serializable源碼
/*
* Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.io;
/**
* Serializability of a class is enabled by the class implementing the
* java.io.Serializable interface. Classes that do not implement this
* interface will not have any of their state serialized or
* deserialized. All subtypes of a serializable class are themselves
* serializable. The serialization interface has no methods or fields
* and serves only to identify the semantics of being serializable.
*
* @author unascribed
* @see java.io.ObjectOutputStream
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutput
* @see java.io.ObjectInput
* @see java.io.Externalizable
* @since JDK1.1
*/
public interface Serializable {
}
中間還省略了很多注釋,再忽略上面留下的注釋革为,發(fā)現(xiàn)這個接口是空的扭粱!沒有字段,沒有方法震檩,只是標(biāo)識一個類的對象是否可序列化(實(shí)現(xiàn)了這個接口琢蛤,就表示可以序列化)。
serialVersionUID的作用
實(shí)現(xiàn)Serializable接口前后并沒有增加新方法抛虏,只是多了一個serialVersionUID博其,其實(shí)這個字段也不是必須的。若不寫serialVersionUID迂猴。程序也能運(yùn)行成功慕淡,不過eclipe會顯示警告。
其實(shí)错忱,Java的序列化機(jī)制是通過在運(yùn)行時判斷類的serialVersionUID來驗(yàn)證版本一致性的。在進(jìn)行反序列化時挂据,JVM會把傳來的字節(jié)流中的serialVersionUID與本地相應(yīng)實(shí)體類的serialVersionUID進(jìn)行比較以清,如果相同就認(rèn)為是一致的,可以進(jìn)行反序列化崎逃,否則就會出現(xiàn)序列化版本不一致的異常(InvalidCastException)掷倔。
所以最好有這個字段,那么要如何添加呢个绍?鼠標(biāo)移動到警告處勒葱,eclipse在給出警告的同時也給出了解決方法:
- 添加默認(rèn)值,即1L;
- 添加自動產(chǎn)生的ID值巴柿,我的是8685376332791485990L;(推薦)
- 通過@SuppressWarnings 批注取消特定代碼段(即凛虽,類或方法)中的警告。
java實(shí)現(xiàn)
package serialize;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
public class LoginInfo implements Serializable{
private static final long serialVersionUID = 8685376332791485990L;
private String username;
private String password;
private Date logindate;
public LoginInfo(){
System.out.println("non-parameter constructor");
}
public LoginInfo(String username, String password){
System.out.println("parameter constructor");
this.username = username;
this.password = password;
this.logindate = new Date();
}
public String toString(){
return "username="+username+",password="+password+",logindate="+logindate;
}
public static void main(String[] args) throws Exception{
LoginInfo info = new LoginInfo("chen","123");
System.out.println(info);
String fileName = "info_serializable.txt";
System.out.println("Serialize object");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
oos.writeObject(info);
oos.close();
System.out.println("Deserialize object");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
LoginInfo info2 = (LoginInfo)ois.readObject();
ois.close();
System.out.println(info2);
}
}
運(yùn)行結(jié)果:
parameter constructor
username=chen,password=123,logindate=Sun Feb 19 09:33:38 CST 2017
Serialize object
Deserialize object
username=chen,password=123,logindate=Sun Feb 19 09:33:38 CST 2017
之后會在項(xiàng)目目錄下生成文件info_serializable.txt广恢,雖然存成了txt格式凯旋,但并不能使用普通文本格式打開,會出現(xiàn)亂碼钉迷。
transicent的作用
如果至非,不希望某些字段被序列化,如LoginInfo中的username糠聪,只需在對應(yīng)字段的定義時使用關(guān)鍵詞transient:
// private String password;
private transient String password;
再次運(yùn)行程序荒椭,結(jié)果如下:
parameter constructor
username=chen,password=123,logindate=Sun Feb 19 09:50:34 CST 2017
Serialize object
Deserialize object
username=chen,password=null,logindate=Sun Feb 19 09:50:34 CST 2017
password反序列化后的結(jié)果為null,達(dá)到了保護(hù)密碼的目的舰蟆。
方式二:實(shí)現(xiàn)Externalizable接口
Externalizable源碼
/*
* Copyright (c) 1996, 2004, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.io;
import java.io.ObjectOutput;
import java.io.ObjectInput;
/**
* @author unascribed
* @see java.io.ObjectOutputStream
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutput
* @see java.io.ObjectInput
* @see java.io.Serializable
* @since JDK1.1
*/
public interface Externalizable extends java.io.Serializable {
/**
* @param out the stream to write the object to
* @exception IOException Includes any I/O exceptions that may occur
*/
void writeExternal(ObjectOutput out) throws IOException;
/**
* @param in the stream to read data from in order to restore the object
* @exception IOException if I/O errors occur
* @exception ClassNotFoundException If the class for an object being
* restored cannot be found.
*/
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
Externalizable繼承了Serializable接口趣惠,并添加了兩個新的方法狸棍,分別表示在哪些字段能被序列化和哪些字段能夠反序列化。
java實(shí)現(xiàn)
erialVersionUID和之前一樣信卡,最好添加隔缀。(但不添加的話,eclipse竟然連警告都沒有0健猾瘸!無奈,只能自己隨便寫個數(shù)了丢习。)
package serialize;
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.Date;
public class LoginInfo2 implements Externalizable{
private static final long serialVersionUID = 4297291454171868241L;
private String username;
private String password;
private Date logindate;
public LoginInfo2(){
System.out.println("non-parameter constructor");
}
public LoginInfo2(String username, String password){
System.out.println("parameter constructor");
this.username = username;
this.password = password;
this.logindate = new Date();
}
public String toString(){
return "username="+username+
",password="+password+
",logindate="+logindate;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(logindate);
out.writeUTF(username);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
logindate = (Date)in.readObject();
username = (String)in.readUTF();
}
public static void main(String[] args) throws Exception{
LoginInfo2 info = new LoginInfo2("chen","123");
System.out.println(info);
String fileName = "info_externalizable.txt";
System.out.println("Serialize object");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
oos.writeObject(info);
oos.close();
System.out.println("Deserialize object");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
LoginInfo2 info2 = (LoginInfo2)ois.readObject();
ois.close();
System.out.println(info2);
}
}
運(yùn)行結(jié)果:
parameter constructor
username=chen,password=123,logindate=Sun Feb 19 10:53:57 CST 2017
Serialize object
Deserialize object
non-parameter constructor
username=chen,password=null,logindate=Sun Feb 19 10:53:57 CST 2017
實(shí)現(xiàn)了和Serializable+transient同樣的目的牵触。
無參構(gòu)造函數(shù)
仔細(xì)分析運(yùn)行結(jié)果,發(fā)現(xiàn)這種方式反序列化的時候竟然調(diào)用了無參構(gòu)造函數(shù)咐低。對于恢復(fù)Serializable對象揽思,完全以它存儲的二進(jìn)制為基礎(chǔ)來構(gòu)造,而不調(diào)用構(gòu)造函數(shù)见擦。而對于一個Externalizable對象钉汗,public的無參構(gòu)造函數(shù)將會被調(diào)用。如果沒有public的無參構(gòu)造函數(shù)鲤屡,運(yùn)行時會報異常(Invalid Class Exception : LoginInfo2 ; no valid constructor...),之后會調(diào)用readExternal 讀取數(shù)據(jù)损痰。源碼的注釋中也做了如下說明:
Object Serialization uses the Serializable and Externalizable interfaces. Object persistence mechanisms can use them as well. Each object to be stored is tested for the Externalizable interface.
- If the object supports Externalizable, the writeExternal method is called. If the object does not support Externalizable and does implement Serializable, the object is saved using ObjectOutputStream.
- When an Externalizable object is reconstructed, an instance is created using the public no-arg constructor, then the readExternal method called. Serializable objects are restored by reading them from an ObjectInputStream.
transcient還有效果嗎
對于externalizable實(shí)現(xiàn)方式的代碼做如下修改:
// private Date logindate;
private transient Date logindate;
運(yùn)行結(jié)果與代碼改動之前一樣,說明transcient在externalizable實(shí)現(xiàn)的類中失效了
總結(jié)
-
兩個流:objectOutputStream酒来、ObjectInputStream
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName)); oos.writeObject(info); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName)); LoginInfo info2 = (LoginInfo)ois.readObject(); ois.close();
-
serialVersionUID的作用
Java的序列化機(jī)制是通過在運(yùn)行時判斷類的serialVersionUID來驗(yàn)證版本一致性的卢未,所以實(shí)現(xiàn)Serializable或Externalizable接口都最好有serialVersionUID這一字段,沒有會報警告堰汉。
-
Serializable與Externalizable的關(guān)系
Serializable是個空接口辽社,用于指示某類的對象是否可以序列化。Externalizable接口繼承了Serializable接口翘鸭,添加了writeExternal滴铅、readExternal方法。
-
transient關(guān)鍵字
在Serializable接口實(shí)現(xiàn)的類中就乓,transient修飾的字段不參與序列化過程失息;在Externalizable接口實(shí)現(xiàn)的類中,transient無效档址。即:transient只能與Serializable搭配使用盹兢。
public class LoginInfo implements Serializable{ private static final long serialVersionUID = 8685376332791485990L; private String username; private transient String password; //不參與序列化過程 ...... }
-
writeExternal、readExternal 與無參構(gòu)造函數(shù)
Externalizable實(shí)現(xiàn)的類對象守伸,序列化與反序列化由writeExternal與readExternal兩個函數(shù)控制(內(nèi)部操作的字段要對應(yīng))绎秒,而且反序列時會先調(diào)用無參構(gòu)造函數(shù)創(chuàng)建實(shí)例對象,再通過readExternal讀取數(shù)據(jù)尼摹。所以Externalizable實(shí)現(xiàn)的類若想反序列化见芹,必須有無參構(gòu)造函數(shù)剂娄。而恢復(fù)Serializable對象,完全以之前存儲的二進(jìn)制為基礎(chǔ)來構(gòu)造玄呛,不調(diào)用構(gòu)造函數(shù)阅懦。