序列化的意義
1.永久存儲某個jvm中運行時的對象。
2.對象可以網(wǎng)絡(luò)傳輸
3.rmi調(diào)用都是以序列化的方式傳輸參數(shù)
基本知識
1.在Java中,只要一個類實現(xiàn)了java.io.Serializable接口,那么它就可以被序列化。
2.通過ObjectOutputStream和ObjectInputStream對對象進(jìn)行序列化及反序列化
3.虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID)
4.序列化并不保存靜態(tài)變量。
5.要想將父類對象也序列化,就需要讓父類也實現(xiàn)Serializable 接口。
6.Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設(shè)為初始值,如 int 型的是 0,對象型的是 null。
7.服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù),對象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時,進(jìn)行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進(jìn)行反序列化時,才可以對密碼進(jìn)行讀取,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全。(自定義實現(xiàn)readObject和writeObject)
一些雜事
1.將統(tǒng)一個對象連續(xù)多次寫入一個文件,Java 序列化機(jī)制為了節(jié)省磁盤空間,具有特定的存儲規(guī)則,當(dāng)寫入文件的為同一對象時,并不會再將對象的內(nèi)容進(jìn)行存儲,而只是再次存儲一份引用,上面增加的 5 字節(jié)的存儲空間就是新增引用和一些控制信息的空間。反序列化時,恢復(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"));
//從文件依次讀出兩個文件
Test t1 = (Test) oin.readObject();
Test t2 = (Test) oin.readObject();
oin.close();
//判斷兩個引用是否指向同一個對象
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實際上是動態(tài)數(shù)組,每次在放滿以后自動增長設(shè)定的長度值,如果數(shù)組自動增長長度設(shè)為100,而實際只放了一個元素,那就會序列化99個null元素。為了保證在序列化的時候不會將這么多null同時進(jìn)行序列化,ArrayList把元素數(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的幾個自定義神器方法
一. 解決反序列化異常的神器——Serializable接口的readObjectNoData方法
- 方法定義:private void readObjectNoData() throws ObjectStreamException
- 這個方法可能會讓你摸不著頭腦,測試的時候怎么都不會被執(zhí)行?,哈哈哈!這里是真相:如果有一個類A正在被反序列化,S是它現(xiàn)在的父類,readObjectNoData方法也應(yīng)該存在與S類中。當(dāng)發(fā)生以下條件時,將觸發(fā)這個readObjectNoData方法被執(zhí)行:
1.S類直接或間接實現(xiàn)了Serializable接口
2.S類必須定義實現(xiàn)readObjectNoData方法
3.當(dāng)前待被反序列化的stream在被序列化時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,如果實現(xiàn)了writeReplace方法后,那么在序列化時會先調(diào)用writeReplace方法將當(dāng)前對象替換成另一個對象(該方法會返回替換后的對象)并將其寫入流中,例如:
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;
}
}
在這里就將該對象直接替換成了一個list保存;
- 實現(xiàn)writeReplace就不要實現(xiàn)writeObject了,實現(xiàn)了也不會被調(diào)用;
- writeReplace的返回值(對象)必須是可序列話的,如果是Java自己的基礎(chǔ)類或者類型那就不用說了;
- writeReplace替換后在反序列化時就無法被恢復(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替換寫入后也不能通過實現(xiàn)readObject來實現(xiàn)自動恢復(fù)了,即下面的代碼是錯誤的!因為默認(rèn)已經(jīng)被徹底替換了,就不存在自定義反序列化的問題了,直接自動反序列化成ArrayList了,該方法即使實現(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 { // 錯!寫了也沒用
ArrayList<Object> list = (ArrayList)in.readObject();
this.name = (String)list.get(0);
this.age = (int)list.get(1);
}
}
所以,writeObject只和readObject配合使用,一旦實現(xiàn)了writeReplace在寫入時進(jìn)行替換就不再需要writeObject和readObject了!因為替換就已經(jīng)是徹底的自定義了,比writeObject/readObject更徹底!
三. 保護(hù)性恢復(fù)對象(同時也可以替換對象)——readResolve:
- readResolve會在readObject調(diào)用之后自動調(diào)用,它最主要的目的就是讓恢復(fù)的對象變個樣,比如readObject已經(jīng)反序列化好了一個Person對象,那么就可以在readResolve里再對該對象進(jìn)行一定的修改,而最終修改后的結(jié)果將作為ObjectInputStream的readObject的返回結(jié)果;
- 可以返回一個非原類型的對象,也就是說可以徹底替換對象
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 { // 直接替換成一個int的1返回
return 1;
}
}
- 其最重要的應(yīng)用就是保護(hù)性恢復(fù)單例、枚舉類型的對象!
枚舉問題示例:
class Brand implements Serializable {
private int val;
private Brand(int val) {
this.val = val;
}
// 兩個枚舉值
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 !!!!!!!!!因為Brand.NIKE是程序中創(chuàng)建的對象,而b是從磁盤中讀取并恢復(fù)過來的對象,兩者明顯來源不同,因此必然內(nèi)存空間是不同的,引用(地址)顯然也是不同的
}
}
}
使用readResolve解決這個問題:
class Brand implements Serializable {
private int val;
private Brand(int val) {
this.val = val;
}
// 兩個枚舉值
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)制自己實現(xiàn)串行化和反串行化——Externalizable接口
- 不像Serializable接口只是一個標(biāo)記接口,里面的接口方法都是可選的(可實現(xiàn)可不實現(xiàn),如果不實現(xiàn)則啟用其自動序列化功能),而Externalizable接口不是一個標(biāo)記接口,它強(qiáng)制你自己動手實現(xiàn)串行化和反串行化算法:
//可以看到該接口直接繼承了Serializable接口,其兩個方法其實就對應(yīng)了Serializable的writeObject和readObject方法,實現(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)制實現(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)行序列化時,當(dāng)讀取對象時,會調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個新的對象,然后再將被保存對象的字段的值分別填充到新對象中。因此,必須提供一個無參構(gòu)造器,訪問權(quán)限為public;否則會拋出java.io.InvalidClassException 異常;Serializable序列化時不會調(diào)用默認(rèn)的構(gòu)造器