對象序列化

對象序列化的目標是將對象保存到磁盤中,或容許網絡中直接傳輸對象。對象序列化機制容許把內存中的對象轉換成平臺無關的二進制流,從而容許這種二進制流持久地保存在磁盤上,經過網絡將這種二進制流傳輸到另外一個網絡節點,其它程序一旦得到這種二進制流,均可以將這種二進制流恢復爲原來的Java對象。java

對象的序列化指將一個Java對象寫入IO流中,與此對應,對象的反序列化指從IO流中恢復該Java對象。
若是須要讓對象支持序列化機制,則必須讓它的類是可序列化的。爲了讓某個類是可序列化的,該類必須實現以下兩個接口之一。算法

  • Serializable
  • Externalizable
    其中Serializable是一個標記接口,實現該接口無須實現任何方法,它只是代表該類的實例是可序列化的。

使用對象流實現序列化

一旦某個類實現了Serializable接口,該類的對象就是可序列化的,程序能夠經過以下兩個步驟來序列化該對象。網絡

  1. 建立ObjectOutputStream流
  2. 調用writeObject(Object obj)方法
public class Person implements Serializable {
    private static final long serialVersionUID = 3232557182439996130L;
    private String name;
    private int age;

    public Person(String name, int age) {
        System.out.println("有參構造器");
        this.name=name;
        this.age=age;
    }

    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"))) {
            Person per = new Person("sunqiang", 30);
            oos.writeObject(per);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

對象引用的序列化

  • 若是某個類的成員變量的類型不是基本類型或String類型,那麼這個引用類必須是可序列化的,不然擁有該類型成員變量的類也是不可序列化的。
  • Java序列化機制採用了一種特殊的序列化算法,全部保存到磁盤中的對象都有一個序列化序號。當程序試圖序列化一個對象時,程序先檢查該對象是否已經被序列化,只有該對象從未被序列化過,系統纔將該對象轉化成字節序列並輸出,若是某個對象已經序列化過,程序將只是直接輸出一個序列化編號,而不是再次從新序列化該對象。
  • 因爲Java序列化機制,若是屢次序列化同一個Java對象,只有第一次纔會把該Java對象轉換成字節序列並輸出,這樣可能存在一個潛在的問題,以下:
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) 
        {
            Person person = new Person("sun", 500);
            Teacher t1 = new Teacher("li", person);

            oos.writeObject(t1);
            t1.setName("改變姓名");
            oos.writeObject(t1);


        } catch (IOException ex) {
            ex.printStackTrace();
        }

兩次將t1對象序列化,但在第二次序列化前改變了t1變量的值,由於t1已經被序列化過一次,再次調用writeObject()不會將該對象寫入,下面讀取對象,能夠看到兩次讀取的對象name屬性相同。ide

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("teacher.txt"))) {
            Teacher t1 = (Teacher) ois.readObject();
            Teacher t2 = (Teacher) ois.readObject();
            System.out.println("t1:name:" + t1.getName());
            System.out.println("t2:name:" + t2.getName());
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (ClassNotFoundException cnf) {
            cnf.printStackTrace();
        }

自定義序列化

transient關鍵字

經過在實例變量前使用transient關鍵字,能夠指定Java序列化時無須理會該實例變量。this

自定義序列化

使用transient修飾的變量被隔離在序列化機制以外,這樣致使在反序列化恢復Java對象是沒法獲取該實例變量的值。Java還提供了一種自定義序列化機制,經過這種機制可讓程序控制如何序列化各實例變量。
有一個Person類:code

public class Person implements Serializable {
    private static final long serialVersionUID = -4271096500079406727L;
    private String name;
    private int age;
    //省略getter,setter方法
}

方法1

爲序列化類提供以下方法:對象

private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;
 private void readObjectNoData()
     throws ObjectStreamException;

下面是一個例子接口

private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }
    private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {
        this.name=((StringBuffer)in.readObject()).reverse().toString();
        this.age=in.readInt();
    }

方法2

爲序列化類提供以下方法:內存

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

writeReplace由序列化機制調用,只要該方法存在,Java在序列化機制運行保證在序列化某個對象以前,先調用該對象的writeReplace()方法。get

private Object writeReplace(){
        ArrayList<Object> list=new ArrayList<>();
        list.add(name);
        list.add(age);
        return list;
    }
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("replace.txt"));
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream("replace.txt"))){
                Person person=new Person("sun",550);
                oos.writeObject(person);
                ArrayList list=(ArrayList)ois.readObject();
                System.out.println(list);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

方法3

爲序列化類提供以下方法:

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

實現Externalizable接口自定義序列化

要實現該接口裏的兩個方法:

@Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.name = ((StringBuffer) in.readObject()).reverse().toString();
        this.age = in.readInt();
    }

須要指出的是當使用Externalizable機制反序列化時,程序會先使用public的無參構造器建立實例,而後才執行readExternal()方法進行反序列化,所以實現Externalizable的序列化類必須提供public的無參構造器。

注意

關於對象序列化,還要注意一下幾點:

  • 對象的類名、實例變量都會被序列化;方法、類變量,transient實例變量都不會被序列化。
  • 保證序列化對象的實例變量也是可序列化的,不然須要使用transient關鍵字來修飾該實例變量,要否則,該類就是不可序列化的。
  • 反序列化對象時必須有序列化對象的class文件
  • 當經過文件、網絡來讀取序列化後的對象時,必須按實際寫入的順序讀取。
相關文章
相關標籤/搜索