序列化與反序列化

序列化基礎:
即使用ObjectOutputStream與ObjectInputStream進行對象與字節流的轉換,一般需要提供一個序列化id。
tip:默認序列化時若一個域被修飾爲transient,則不序列化該實例域。
import java.io.*;
public class Test {
    public static void main(String[] args) throws Exception{
        //將兩個對象序列化存儲到文件中
        File f = new File("oos.txt");
        System.out.println(f.exists());
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
        oos.writeObject(new T(1));
        oos.writeObject(new T(2));
        oos.close();
        //從序列化文件反序列化生成兩個對象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
        T t1 = (T)ois.readObject();
        T t2 = (T)ois.readObject();
        t1.get();
        t2.get();
        ois.close();
    }
}
class T implements Serializable {
    private int x;
    public T(int x){
        this.x = x;
    }
    public void get(){
        System.out.println(x);
    }
    /**
     * Serilizable接口沒有抽象方法,所以以下四個方法可以不寫
     *以下四個方法,爲自定義序列化時的可選方法,將由ObjectOutputStream
     * 與ObjectInputStream進行反射調用。
     */
    //此方法在寫入序列化文件時最先被調用,其返回一個Serilizable對象用於代替當前對象進行序列化
    private Object writeReplace(){
        return new T(5);
    }
    //此方法用於選擇保存當前對象的關鍵域(決定這個對象的實例域)到序列化文件
    private void writeObject(ObjectOutputStream os) throws Exception {
        //爲了往後兼容
        os.defaultWriteObject();
        os.writeInt(x);
    }
    //此方法用於從序列化文件中獲取數據用來恢復關鍵域
    private void readObject(ObjectInputStream is) throws Exception{
        //爲了往後兼容
        is.defaultReadObject();
        x  = is.readInt();
    }
    //此方法在恢復對象時最後被調用,其返回一個對象用於替代文件恢復的對象,一般用於序列化代理
    private Object readResolve(){
        return new T(4);
    }
}
序列化高級:
謹慎地實現Serilizable接口,其代價如下
一旦類被公佈,就降低了修改這個類的可能性
增加了bug和可能問題,可能破壞singleton模式
測試負擔增加
考慮自定義的序列化形式
考慮以下的StringList類,若使用默認的自定義形式,其將對head進行序列化,因此對鏈表的每個節點進行序列化,一來,增大了序列化的大小;二來,使得字符串列表限制只能使用鏈表Entry實現;三來,增大
了序列化時間,其將對previous與next均進行序列化,需要有昂貴的圖遍歷過程,而我們可以簡單調用next獲得字符串列表;四來,在元素多時,遞歸序列化可能造成棧溢出。
import java.io.Serializable;
/**
 * Created by Doggy on 2015/9/13.
 */
public final class StringList implements Serializable{
    private int size = 0;
    private Entry head = null;
    private static class Entry implements Serializable{
        private Entry previous;
        private Entry next;
        private String value;
    }
}
因爲對於字符串列表來說只關心字符串個數與順序,所以可以採用以下自定義的序列化方法代替,
自定義序列化時大部分實例域應該被標記爲transient(一個域被聲明爲transient,則其反序列化的值對於int0,引用則爲null,直到執行readObject纔會初始化)
編寫一個線程安全的可序列化類需要對readObject以及writeObject加鎖
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
 * Created by Doggy on 2015/9/13.
 */
public final class StringList implements Serializable{
    //修飾爲transient避免默認序列化時序列化該實例域
    private transient int size = 0;
    private transient Entry head = null;
    //添加一個增加字符串的方法
    private final void addOne(String s){
        Entry ent = new Entry();
        ent.value = s;
        Entry tmp = head;
        while(tmp.next != null){
            tmp = tmp.next;
        }
        ent.previous = tmp;
        tmp.next = ent;
    }
    private static class Entry implements Serializable{
        private Entry previous;
        private Entry next;
        private String value;
    }
    //編寫writeObject進行自定義序列化
    private void writeObject(ObjectOutputStream os) throws Exception{
        //爲了向後拓展,後期在類中加入一些實例域可能有用
        os.defaultWriteObject();
        //寫入字符串列表的大小
        os.writeInt(size);
        //將字符串列表中的每個字符串按順序寫入文件
        while(tmp.next != null){
            os.writeObject(tmp.value);
            tmp = tmp.next;
        }
    }
    private void readObject(ObjectInputStream is) throws Exception{
        //爲了向後拓展,後期在類中加入一些實例域可能有用
        is.defaultReadObject();
        //讀取大小到對象中
        size = is.readInt();
        //根據列表元素以及addOne方法進行恢復
        for (int i = 0; i < size; i++) {
            addOne((String)is.readObject());
        }
    }
}

保護性地編寫readObject方法
/*
     *readObject應該與構造器類似,不能(間接)調用一個可覆蓋的方法
     * 且應該實現與構造器一致的有效性檢測與保護性拷貝(防止內部實例域引用泄露)
     */
    private void readObject(ObjectInputStream is) throws Exception{
        //爲了向後拓展,後期在類中加入一些實例域可能有用
        is.defaultReadObject();
        //保護性拷貝,若不實現,則可能通過僞造字節流,獲得對start與end的引用,在客戶端修改該類的start、end域,影響類的不可變性
        start = new Date(start.getTime());
        end = new Date(end.getTime());
        //數據有效性檢測
        if(start.compareTo(end) > 0){
            throw new InvalidObjectException();
        }
    }

枚舉單例優先於使用readResolve控制的序列化單例
//可以在readResolve中直接返回單例對象,但所有實例域必須被聲明爲transient
//否則在未執行readResolve之前的readObject產生的新單例對象可能被盜用。
private Object readResolve(){
    return INSTANCE;
}
考慮使用序列化代理代替序列化實例
/**
 * 好處是外部類的所有實例都是從構造器創建,
 * 所以可以防止以上的僞造流以及盜用者造成的危害
 * 也不用特別檢測數據的有效性,因爲在構造器中已經檢測過
 */
class Period{
    private final Date start;
    private final Date end;
    public Period(Date start,Date end){
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());
    //數據有效性檢測
        if(start.compareTo(end) > 0){
            throw new InvalidParameterException();
        }
    }
    //使用writeReplace將序列化任務轉發給代理,所以不存在任何外部類的序列化實例
    private Object writeReplace(){
        return new PeriodProxy(start,end);
    }
    //防止對外部類使用字節流創建對象,直接對僞造流拋異常
    private void readObject(){
        throw new InvalidObjectException();
    }
    //這裏要是static,否則調用defaultWriteObject時,會將類信息寫入,導致讀異常。
    private static class PeriodProxy{
        private final Date start;
        private final Date end;
        private PeriodProxy(Date start,Date end){
            this.start = start;
            this.end = end;
        }
        //自定義實現readObject和writeObject
        //        ...
        //實現writeResolve,將內部代理轉換爲外部類對象
        private Object readResolve(){
            //若爲單例則直接返回INSTANCE
            return new Period(start,end);
        }
    }
}
發佈了45 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章