原帖地址:原帖
個(gè)人網(wǎng)站地址:個(gè)人網(wǎng)站
簡書對markdown的支持太完美了,我竟然可以直接Ctrl C/V過來。
定義
Java序列化是指把Java對象轉(zhuǎn)換為字節(jié)序列的過程;
Java反序列化是指把字節(jié)序列恢復(fù)為Java對象的過程。
應(yīng)用場景
當(dāng)兩個(gè)進(jìn)程進(jìn)行遠(yuǎn)程通信時(shí),可以相互發(fā)送各種類型的數(shù)據(jù),包括文本、圖片、音頻、視頻等, 而這些數(shù)據(jù)都會以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送。那么當(dāng)兩個(gè)Java進(jìn)程進(jìn)行通信時(shí),如何實(shí)現(xiàn)進(jìn)程間的對象傳送呢?這就需要Java序列化與反序列化了。一方面,發(fā)送方需要把這個(gè)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,這個(gè)使用的比較多。Serializable接口是一個(gè)空的接口,它的主要作用就是標(biāo)識這個(gè)類的對象是可序列化的。
- 實(shí)現(xiàn)接口Externalizable。Exterinable繼承了Serializable,是Serializable的一個(gè)擴(kuò)展,對于哪些屬性可以序列化,哪些可以反序列化可以做詳細(xì)地約束。
相關(guān)工具類:
-
java.io.ObjectOutputStream:表示對象輸出流
它是OutputStream類的一個(gè)子類,對應(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"); //某個(gè)待序列化對象,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é)序列,再把它們反序列化成為一個(gè)對象,并返回。
使用方式如下:
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)這個(gè)接口是空的!沒有字段,沒有方法,只是標(biāo)識一個(gè)類的對象是否可序列化(實(shí)現(xiàn)了這個(gè)接口,就表示可以序列化)。
serialVersionUID的作用
實(shí)現(xiàn)Serializable接口前后并沒有增加新方法,只是多了一個(gè)serialVersionUID,其實(shí)這個(gè)字段也不是必須的。若不寫serialVersionUID。程序也能運(yùn)行成功,不過eclipe會顯示警告。
其實(shí),Java的序列化機(jī)制是通過在運(yùn)行時(shí)判斷類的serialVersionUID來驗(yàn)證版本一致性的。在進(jìn)行反序列化時(shí),JVM會把傳來的字節(jié)流中的serialVersionUID與本地相應(yīng)實(shí)體類的serialVersionUID進(jìn)行比較,如果相同就認(rèn)為是一致的,可以進(jìn)行反序列化,否則就會出現(xiàn)序列化版本不一致的異常(InvalidCastException)。
所以最好有這個(gè)字段,那么要如何添加呢?鼠標(biāo)移動(dòng)到警告處,eclipse在給出警告的同時(shí)也給出了解決方法:
- 添加默認(rèn)值,即1L;
- 添加自動(dòng)產(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)字段的定義時(shí)使用關(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接口,并添加了兩個(gè)新的方法,分別表示在哪些字段能被序列化和哪些字段能夠反序列化。
java實(shí)現(xiàn)
erialVersionUID和之前一樣,最好添加。(但不添加的話,eclipse竟然連警告都沒有!!無奈,只能自己隨便寫個(gè)數(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)這種方式反序列化的時(shí)候竟然調(diào)用了無參構(gòu)造函數(shù)。對于恢復(fù)Serializable對象,完全以它存儲的二進(jìn)制為基礎(chǔ)來構(gòu)造,而不調(diào)用構(gòu)造函數(shù)。而對于一個(gè)Externalizable對象,public的無參構(gòu)造函數(shù)將會被調(diào)用。如果沒有public的無參構(gòu)造函數(shù),運(yùn)行時(shí)會報(bào)異常(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é)果與代碼改動(dòng)之前一樣,說明transcient在externalizable實(shí)現(xiàn)的類中失效了
總結(jié)
-
兩個(gè)流: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)行時(shí)判斷類的serialVersionUID來驗(yàn)證版本一致性的,所以實(shí)現(xiàn)Serializable或Externalizable接口都最好有serialVersionUID這一字段,沒有會報(bào)警告。
-
Serializable與Externalizable的關(guān)系
Serializable是個(gè)空接口,用于指示某類的對象是否可以序列化。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兩個(gè)函數(shù)控制(內(nèi)部操作的字段要對應(yīng)),而且反序列時(shí)會先調(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ù)。