1. 程式人生 > >筆記:I/O流-對象序列化

筆記:I/O流-對象序列化

err extends 自己 point clas xtend his size cto

Java 語言支持一種稱為對象序列化(Object Serialization)的非常通用的機制,可以將任何對象寫入到流中,並在之後將其讀回,首先需要支持對象序列化的類,必須繼承與 Serializable 接口,該接口沒有任何方法,只是對類起到標記的作用,然後使用 ObjectOutputStream 流來序列化對象,使用 ObjectInputStream 流來反序列化,示例代碼如下:

  • 對象類聲明:

    public class Employee implements Serializable {

    ????????private String name;

    ????????private String sex;

    ????????public Employee() {

    ????????}

    ????????public Employee(String name, String sex) {

    ????????????????this.name = name;

    ????????????????this.sex = sex;

    ????????}

    ????????// getter 和 setter 方法

    }

    ? ?

    public class Manager extends Employee {

    ????????private Employee secretary;

    ????????public Manager(){

    ????????}

    ????????public Manager(String name, String sex) {

    ????????????????super(name, sex);

    ????????}

    ????????// getter 和 setter 方法

    }

  • 創建對象實例:

    ?Employee harry = new Employee("Harry Hacker", "男");

    ?Manager boss = new Manager("Carl Cracker", "女");

    boss.setSecretary(harry);

  • 序列化到文件

    ? ObjectOutputStream outputStream = null;

    ?????????try {

    ??????????????? outputStream = new ObjectOutputStream(new FileOutputStream("serializableApp.dat"));

    ????????????????outputStream.writeObject(boss);

    ???????? } catch (FileNotFoundException ex) {

    ????????????????ex.printStackTrace();

    ???????? ?} finally {

    ????????????????if (outputStream != null) {

    ???????????????????????outputStream.close();

    ????????????????}

    ??????? ?}

  • 從文件反序列化

????????????????ObjectInputStream inputStream = null;

????????????????try {

????????????????????????inputStream = new ObjectInputStream(new FileInputStream("serializableApp.dat"));

????????????????????????Manager serializableBoss = (Manager) inputStream.readObject();

????????????????????????System.out.println("manager name is " + serializableBoss.getName() + " sex is "

????????????????????????????????????????+ serializableBoss.getSex() + " secretary is "

????????????????????????????????????????+ serializableBoss.getSecretary().getName());

????????????????} catch (ClassNotFoundException ex) {

????????????????????????ex.printStackTrace();

????????????????} catch (FileNotFoundException ex) {

????????????????????????ex.printStackTrace();

????????????????} finally {

????????????????????????if (inputStream != null) {

????????????????????????????????inputStream.close();

????????????????????????}

????????????????}

每個對象都用一個序列號保存的,對象序列化機制如下:

  • 對於遇到的每一個對象引用都關聯一個序列號
  • 對於每一個對象,當第一次遇到時,保存器對象數據到流中
  • 如果某個對象已經被保存過,那麽只寫出保存的序列號
  • 對於流中的對象,在第一次遇到其序列號時,創建他,並使用流中數據來初始化他,然後記錄這個順序號和新對象之間的關聯
  • 當遇到對象引用另一個對象的序列號時,獲取與這個序列號相關聯的對象引用

某些數據域時不可以序列化的,例如,只對本地方法有意義的存儲文件句柄或窗口句柄的整數值等,Java 擁有一種簡單的機制來防止這種域被序列化,那就是將他們標記成 transient,如果被標記為不可序列化的類,也需要將其標記為 transient,瞬時域在對象序列化時總是被跳過,示例如下:

????private transient Point2D.Double point;

? ?

  1. 修改默認的序列化機制

    序列化機制單個的類提供了一種方式,去向默認的讀寫行為添加驗證或任何其他想要的行為,可序列化類可以定義具體有如下簽名的方法:

    ????????private void readObject(ObjectInputStream in)

    ????????????????????????throws IOException,ClassNotFoundException;

    ???????? ?

    ????????private void writeObject(ObjectOutputStream out)

    ????????????????????????throws IOException;

    readObject writeObject 方法只需要保存和加載本類的數據域,而不需要關註基類(超類)數據和任何其他類的信息,實現給方法的具體示例如下:

    ???? private void readObject(ObjectInputStream in)

    ????????????????????????throws IOException, ClassNotFoundException {

    ????????????????// 讀取序列化字段數據

    ????????????????in.defaultReadObject();

    ????????????????// 其他數據校驗或者序列化

    ????????}

    ????????private void writeObject(ObjectOutputStream out)

    ????????????????????????throws IOException{

    ????????????????// 寫入序列化字段數據

    ????????????????out.defaultWriteObject();

    ????????????????// 其他數據校驗或者序列化

    ???????}

    除了可以使用readObject writeObject方法來保存和恢復對象數據外,類還可以定義他自己的機制,類需要實現 Externalizable 接口,該接口定義了兩個方法:

    ????public void readExternal(ObjectInput in)

    ???????? throws IOException, ClassNotFoundException;

    ? ?

    ????public void writeExternal(ObjectOutput out)

    ????????????throws IOException ;

    這些方法對包括超類數據在內的整個對象的存儲和恢復負全責,而序列化機制在流中僅僅只是記錄該對象所屬的類,示例代碼如下:

  • 基類代碼:

    ????????public void writeExternal(ObjectOutput out) throws IOException {

    ????????????????out.writeUTF(this.name);

    ????????????????out.writeUTF(this.sex);

    ????????}

    ????????public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    ????????????????this.name = in.readUTF();

    ????????????????this.sex = in.readUTF();

    ????????}

  • 子類代碼:

    @Override

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    ????????super.readExternal(in);

    ????????this.secretary = new Employee();

    ??????? this.secretary.readExternal(in);

    ?}

    @Override

    public void writeExternal(ObjectOutput out) throws IOException {

    ????????super.writeExternal(out);

    ???????this.secretary.writeExternal(out);

    ?}

    ? ?

  1. 序列化單例和類型安全的枚舉

    在序列化和反序列化時,如果目標對象時唯一的,使用默認的序列化機制時不適用的,因為默認的序列化機制,即使構造器時私有的,序列化機制也可以創建新的對象,因此在進行==(比較)時將失敗,為了解決整個問題需要定義一個名稱為 readResolve的特殊方法,在對象被序列化之後就會調用他,返回一個對象,而該對象之後會稱為 readObject 的返回值,示例代碼如下:

    public class Orientation implements Serializable {

    ????????public static final Orientation HORIZONTAL = new Orientation(1);

    ????????public static final Orientation VERTICAL = new Orientation(2);

    ? ?

    ????????private int value;

    ? ?

    ????????private Orientation(int value) {

    ????????????????this.value = value;

    ????????}

    ? ?

    ????????protected Object readResolve() throws ObjectStreamException {

    ????????????????if (value == 1) {

    ????????????????????????return HORIZONTAL;

    ????????????????}

    ????????????????if (value == 2) {

    ????????????????????????return VERTICAL;

    ????????????????}

    ???????????????? ?

    ????????????????return null;

    ????????}

    }

  2. 版本管理

    無論類的定義產生了什麽樣的變化,他的SHA指紋也會跟著變化,而我們知道對象流拒絕讀入具有不同指紋的對象,但是,類可以表明他對其早期版本保持兼容,在類的所有較新的版本都必須把 serialVersionUID 常量定義與最初版本的指紋相同,如果一個類具有名為 serialVersionUID 的靜態數據成員,就不需要在人工的計算其指紋,而只需直接使用整個值,示例如下:

    public class Employee implements Serializable, Externalizable {

    ????????public static final long serialVersionUID = -2349238498234324L;

    }

    如果類只有方法產生了變化,那麽在讀入新對象數據時是不會有任何問題的,如果是數據域產生了變化,那麽就可能會有問題,常見情況如下:

  • 如果數據域之間名字匹配而類型不匹配,那麽對象流不會進行類型轉換,因此不兼容
  • 如果流中的對象具有當前版本中所沒有的數據域,那麽對象流會忽視這些額外的數據
  • 如果當前版本具有在流化對象中所沒有的數據域,那麽這些新增加的域將被設置成他們的默認值(對象是null、數字為 0,布爾類型為 false)

? ?

筆記:I/O流-對象序列化