序列化的意義
1.永久存儲某個(gè)jvm中運(yùn)行時(shí)的對象历谍。
2.對象可以網(wǎng)絡(luò)傳輸
3.rmi調(diào)用都是以序列化的方式傳輸參數(shù)
基本知識
1.在Java中望侈,只要一個(gè)類實(shí)現(xiàn)了java.io.Serializable接口脱衙,那么它就可以被序列化捐韩。
2.通過ObjectOutputStream和ObjectInputStream對對象進(jìn)行序列化及反序列化
3.虛擬機(jī)是否允許反序列化荤胁,不僅取決于類路徑和功能代碼是否一致仅政,一個(gè)非常重要的一點(diǎn)是兩個(gè)類的序列化 ID 是否一致(就是 private static final long serialVersionUID)
4.序列化并不保存靜態(tài)變量。
5.要想將父類對象也序列化,就需要讓父類也實(shí)現(xiàn)Serializable 接口运褪。
6.Transient 關(guān)鍵字的作用是控制變量的序列化秸讹,在變量聲明前加上該關(guān)鍵字雅倒,可以阻止該變量被序列化到文件中蔑匣,在被反序列化后,transient 變量的值被設(shè)為初始值凿将,如 int 型的是 0牧抵,對象型的是 null侨把。
7.服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù),對象中有一些數(shù)據(jù)是敏感的获枝,比如密碼字符串等映琳,希望對該密碼字段在序列化時(shí)萨西,進(jìn)行加密旭旭,而客戶端如果擁有解密的密鑰持寄,只有在客戶端進(jìn)行反序列化時(shí),才可以對密碼進(jìn)行讀取荠卷,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全烛愧。(自定義實(shí)現(xiàn)readObject和writeObject)
一些雜事
1.將統(tǒng)一個(gè)對象連續(xù)多次寫入一個(gè)文件怜姿,Java 序列化機(jī)制為了節(jié)省磁盤空間沧卢,具有特定的存儲規(guī)則,當(dāng)寫入文件的為同一對象時(shí)披诗,并不會再將對象的內(nèi)容進(jìn)行存儲藤巢,而只是再次存儲一份引用掂咒,上面增加的 5 字節(jié)的存儲空間就是新增引用和一些控制信息的空間迈喉。反序列化時(shí)挨摸,恢復(fù)引用關(guān)系得运,使得清單 3 中的 t1 和 t2 指向唯一的對象熔掺,二者相等,輸出 true推沸。該存儲規(guī)則極大的節(jié)省了存儲空間鬓催。
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
Test test = new Test();
//試圖將對象兩次寫入文件
out.writeObject(test);
out.flush();
System.out.println(new File("result.obj").length());
out.writeObject(test);
out.close();
System.out.println(new File("result.obj").length());
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
//從文件依次讀出兩個(gè)文件
Test t1 = (Test) oin.readObject();
Test t2 = (Test) oin.readObject();
oin.close();
//判斷兩個(gè)引用是否指向同一個(gè)對象
System.out.println(t1 == t2);
2.arraylist源碼倍靡,elementData為什么是transient的
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
transient Object[] elementData; // non-private to simplify nested class access
private int size;
}
ArrayList實(shí)際上是動態(tài)數(shù)組课舍,每次在放滿以后自動增長設(shè)定的長度值,如果數(shù)組自動增長長度設(shè)為100雨让,而實(shí)際只放了一個(gè)元素,那就會序列化99個(gè)null元素崔挖。為了保證在序列化的時(shí)候不會將這么多null同時(shí)進(jìn)行序列化狸相,ArrayList把元素?cái)?shù)組設(shè)置為transient脓鹃。
3.自定義序列化方式writeObject,readObject
以arraylist的readObject為例
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject(); //如果不自己定義readObject方法娇跟,默認(rèn)就是調(diào)用ObjectInputStream的defaultReadObject方法。
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
Serializable的幾個(gè)自定義神器方法
一. 解決反序列化異常的神器——Serializable接口的readObjectNoData方法
- 方法定義:private void readObjectNoData() throws ObjectStreamException
- 這個(gè)方法可能會讓你摸不著頭腦吃谣,測試的時(shí)候怎么都不會被執(zhí)行岗憋?澜驮,哈哈哈惋鸥!這里是真相:如果有一個(gè)類A正在被反序列化,S是它現(xiàn)在的父類飞蚓,readObjectNoData方法也應(yīng)該存在與S類中趴拧。當(dāng)發(fā)生以下條件時(shí)著榴,將觸發(fā)這個(gè)readObjectNoData方法被執(zhí)行:
1.S類直接或間接實(shí)現(xiàn)了Serializable接口
2.S類必須定義實(shí)現(xiàn)readObjectNoData方法
3.當(dāng)前待被反序列化的stream在被序列化時(shí)A類還沒有進(jìn)程S類脑又。
The readObjectNoData() method is analogous to the class-defined readObject() method, except that (if defined) it is called in cases where the class descriptor for a superclass of the object being deserialized (and hence the object data described by that class descriptor) is not present in the serialization stream.
Note that readObjectNoData() is never invoked in cases where a class-defined readObject() method could be called, though serializable class implementors can call readObjectNoData() from within readObject() as a means of consolidating initialization code.
二. writeReplace问麸,如果實(shí)現(xiàn)了writeReplace方法后严卖,那么在序列化時(shí)會先調(diào)用writeReplace方法將當(dāng)前對象替換成另一個(gè)對象(該方法會返回替換后的對象)并將其寫入流中布轿,例如:
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return name + "(" + age + ")";
}
private Object writeReplace() throws ObjectStreamException {
ArrayList<Object> list = new ArrayList<>();
list.add(name);
list.add(age);
return list;
}
}
在這里就將該對象直接替換成了一個(gè)list保存稠肘;
- 實(shí)現(xiàn)writeReplace就不要實(shí)現(xiàn)writeObject了启具,實(shí)現(xiàn)了也不會被調(diào)用;
- writeReplace的返回值(對象)必須是可序列話的鲁冯,如果是Java自己的基礎(chǔ)類或者類型那就不用說了薯演;
- writeReplace替換后在反序列化時(shí)就無法被恢復(fù)了跨扮,即對象被徹底替換了帝嗡!也就是說使用ObjectInputStream讀取的對象只能是被替換后的對象哟玷,只能在讀取后自己手動恢復(fù)了一也,接著上例的演示:
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return name + "(" + age + ")";
}
private Object writeReplace() throws ObjectStreamException {
ArrayList<Object> list = new ArrayList<>();
list.add(name);
list.add(age);
return list;
}
}
public class Test {
public static void print(String s) {
System.out.println(s);
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.buf"))) {
Person p = new Person("lala", 33);
oos.writeObject(p);
oos.close();
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.buf"))) {
print(((ArrayList)ois.readObject()).toString());
}
}
}
- 使用writeReplace替換寫入后也不能通過實(shí)現(xiàn)readObject來實(shí)現(xiàn)自動恢復(fù)了,即下面的代碼是錯(cuò)誤的谦絮!因?yàn)槟J(rèn)已經(jīng)被徹底替換了须误,就不存在自定義反序列化的問題了京痢,直接自動反序列化成ArrayList了,該方法即使實(shí)現(xiàn)了也不會調(diào)用疲陕!
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return name + "(" + age + ")";
}
private Object writeReplace() throws ObjectStreamException {
ArrayList<Object> list = new ArrayList<>();
list.add(name);
list.add(age);
return list;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // 錯(cuò)方淤!寫了也沒用
ArrayList<Object> list = (ArrayList)in.readObject();
this.name = (String)list.get(0);
this.age = (int)list.get(1);
}
}
所以,writeObject只和readObject配合使用蹄殃,一旦實(shí)現(xiàn)了writeReplace在寫入時(shí)進(jìn)行替換就不再需要writeObject和readObject了携茂!因?yàn)樘鎿Q就已經(jīng)是徹底的自定義了,比writeObject/readObject更徹底诅岩!
三. 保護(hù)性恢復(fù)對象(同時(shí)也可以替換對象)——readResolve:
- readResolve會在readObject調(diào)用之后自動調(diào)用讳苦,它最主要的目的就是讓恢復(fù)的對象變個(gè)樣,比如readObject已經(jīng)反序列化好了一個(gè)Person對象吩谦,那么就可以在readResolve里再對該對象進(jìn)行一定的修改鸳谜,而最終修改后的結(jié)果將作為ObjectInputStream的readObject的返回結(jié)果;
- 可以返回一個(gè)非原類型的對象式廷,也就是說可以徹底替換對象
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return name + "(" + age + ")";
}
private Object readResolve() throws ObjectStreamException { // 直接替換成一個(gè)int的1返回
return 1;
}
}
- 其最重要的應(yīng)用就是保護(hù)性恢復(fù)單例蝗肪、枚舉類型的對象!
枚舉問題示例:
class Brand implements Serializable {
private int val;
private Brand(int val) {
this.val = val;
}
// 兩個(gè)枚舉值
public static final Brand NIKE = new Brand(0);
public static final Brand ADDIDAS = new Brand(1);
}
public class Test {
public static void print(String s) {
System.out.println(s);
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.buf"))) {
oos.writeObject(Brand.NIKE);
oos.close();
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.buf"))) {
Brand b = (Brand)ois.readObject();
print("" + (b == Brand.NIKE)); // 答案顯然是false !!!!!!!!!因?yàn)锽rand.NIKE是程序中創(chuàng)建的對象,而b是從磁盤中讀取并恢復(fù)過來的對象,兩者明顯來源不同,因此必然內(nèi)存空間是不同的,引用(地址)顯然也是不同的
}
}
}
使用readResolve解決這個(gè)問題:
class Brand implements Serializable {
private int val;
private Brand(int val) {
this.val = val;
}
// 兩個(gè)枚舉值
public static final Brand NIKE = new Brand(0);
public static final Brand ADDIDAS = new Brand(1);
//這樣就能保證反序列化回來的枚舉能相等。
private Object readResolve() throws ObjectStreamException {
if (val == 0) {
return NIKE;
}
if (val == 1) {
return ADDIDAS;
}
return null;
}
}
四. 強(qiáng)制自己實(shí)現(xiàn)串行化和反串行化——Externalizable接口
- 不像Serializable接口只是一個(gè)標(biāo)記接口么库,里面的接口方法都是可選的(可實(shí)現(xiàn)可不實(shí)現(xiàn),如果不實(shí)現(xiàn)則啟用其自動序列化功能),而Externalizable接口不是一個(gè)標(biāo)記接口胁澳,它強(qiáng)制你自己動手實(shí)現(xiàn)串行化和反串行化算法:
//可以看到該接口直接繼承了Serializable接口,其兩個(gè)方法其實(shí)就對應(yīng)了Serializable的writeObject和readObject方法,實(shí)現(xiàn)方式也是一模一樣;
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException; // 串行化
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; // 反串行化
}
- ObjectOutput和ObjectInput的使用方法和ObjectOutputStream和ObjectInputStream一模一樣(readObject扬卷、readInt之類的,完全一模一樣)蚕断,因此Externalizable就是強(qiáng)制實(shí)現(xiàn)版的Serializable罷了财岔;
class Person implements Externalizable {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
this.name = ((StringBuffer)in.readObject()).reverse().toString();
this.age = in.readInt();
}
}
- 和Serializable的主要區(qū)別:
1.效率比Serializable高
2.但Serializable更加靈活夷恍,其最重要的特色就是可以自動序列化指黎,這也是其應(yīng)用更廣泛的原因
3.使用Externalizable進(jìn)行序列化時(shí),當(dāng)讀取對象時(shí),會調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個(gè)新的對象,然后再將被保存對象的字段的值分別填充到新對象中。因此,必須提供一個(gè)無參構(gòu)造器,訪問權(quán)限為public;否則會拋出java.io.InvalidClassException 異常;Serializable序列化時(shí)不會調(diào)用默認(rèn)的構(gòu)造器