Java對象序列化 精要

Java對象序列化

      在網上看了很多有關序列化的文章,我自己也寫了兩篇,現在感覺這些文章都沒有很好的把序列化說清楚(包括我自己在內),所以在此我將總結前人以及自己的經驗,用更淺顯易懂的語言來描述該機制,當然,仍然會有不好的地方,希望你看後可以指出,作爲一名程序員應該具有不斷探索的精神和強烈的求知慾望!

序列化概述:

      簡單來說序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化,流的概念這裏不用多說(就是I/O),我們可以對流化後的對象進行讀寫操作,也可將流化後的對象傳輸於網絡之間(注:要想將對象傳輸於網絡必須進行流化)!在對對象流進行讀寫操作時會引發一些問題,而序列化機制正是用來解決這些問題的!

問題的引出:

      如上所述,讀寫對象會有什麼問題呢?比如:我要將對象寫入一個磁盤文件而後再將其讀出來會有什麼問題嗎?別急,其中一個最大的問題就是對象引用!舉個例子來說:假如我有兩個類,分別是A和B,B類中含有一個指向A類對象的引用,現在我們對兩個類進行實例化{ A a = new A(); B b = new B(); },這時在內存中實際上分配了兩個空間,一個存儲對象a,一個存儲對象b,接下來我們想將它們寫入到磁盤的一個文件中去,就在寫入文件時出現了問題!因爲對象b包含對對象a的引用,所以系統會自動的將a的數據複製一份到b中,這樣的話當我們從文件中恢復對象時(也就是重新加載到內存中)時,內存分配了三個空間,而對象a同時在內存中存在兩份,想一想後果吧,如果我想修改對象a的數據的話,那不是還要搜索它的每一份拷貝來達到對象數據的一致性,這不是我們所希望的!

以下序列化機制的解決方案:

1.保存到磁盤的所有對象都獲得一個序列號(1, 2, 3等等)

2.當要保存一個對象時,先檢查該對象是否被保存了。

3.如果以前保存過,只需寫入"與已經保存的具有序列號x的對象相同"的標記,否則,保存該對象

通過以上的步驟序列化機制解決了對象引用的問題!

序列化的實現:

      將需要被序列化的類實現Serializable接口,該接口沒有需要實現的方法,implements Serializable只是爲了標註該對象是可被序列化的,然後使用一個輸出流(如:FileOutputStream)來構造一個ObjectOutputStream(對象流)對象,接着,使用ObjectOutputStream對象的writeObject(Object obj)方法就可以將參數爲obj的對象寫出(即保存其狀態),要恢復的話則用輸入流。

例子:

import java.io.*;

public class Test
{
    public static void main(String[] args)
    {
        Employee harry = new Employee("Harry Hacker", 50000);
        Manager manager1 = new Manager("Tony Tester", 80000);
        manager1.setSecretary(harry);
       
        Employee[] staff = new Employee[2];
       
        staff[0] = harry;
        staff[1] = manager1;
        try
        {
            ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("employee.dat"));
            out.writeObject(staff);
            out.close();
           
            ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("employee.dat"));
            Employee[] newStaff = (Employee[])in.readObject();
            in.close();
  
            /**
             *通過harry對象來加薪
             *將在secretary上反映出來
             */
            newStaff[0].raiseSalary(10);
           
            for (int i = 0; i < newStaff.length; i++)
                System.out.println(newStaff[i]);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
   
}

class Employee implements Serializable
{
    public Employee(String n, double s)
    {
        name = n;
        salary = s;
    }
   
    /**
     *加薪水
     */
    public void raiseSalary(double byPercent)
    {
        double raise = salary * byPercent / 100;
        salary += raise;
    }
   
    public String toString()
    {
        return getClass().getName()
            + "[name = "+ name
            + ",salary = "+ salary
            + "]";
    }
   
    private String name;
    private double salary;
}

class Manager extends Employee
{
    public Manager(String n, double s)
    {
        super(n, s);
        secretary = null;
    }
   
    /**
     *設置祕書
     */
    public void setSecretary(Employee s)
    {
        secretary = s;
    }
   
    public String toString()
    {
        return super.toString()
            + "[secretary = "+ secretary
            + "]";
    }
   
    //secretary代表祕書
    private Employee secretary; 
}

修改默認的序列化機制:   

      在序列化的過程中,有些數據字段我們不想將其序列化,對於此類字段我們只需要在定義時給它加上transient關鍵字即可,對於transient字段序列化機制會跳過不會將其寫入文件,當然也不可被恢復。但有時我們想將某一字段序列化,但它在SDK中的定義卻是不可序列化的類型,這樣的話我們也必須把他標註爲transient,可是不能寫入又怎麼恢復呢?好在序列化機制爲包含這種特殊問題的類提供瞭如下的方法定義:

private void readObject(ObjectInputStream in) throws

         IOException, ClassNotFoundException;

private void writeObject(ObjectOutputStream out) throws

         IOException;

(注:這些方法定義時必須是私有的,因爲不需要你顯示調用,序列化機制會自動調用的)

使用以上方法我們可以手動對那些你又想序列化又不可以被序列化的數據字段進行寫出和讀入操作。

      下面是一個典型的例子,java.awt.geom包中的Point2D.Double類就是不可序列化的,因爲該類沒有實現Serializable接口,在我的例子中將把它當作LabeledPoint類中的一個數據字段,並演示如何將其序列化!

import java.io.*;
import java.awt.geom.*;

public class TransientTest
{
    public static void main(String[] args)
    {
        LabeledPoint label = new LabeledPoint("Book", 5.00, 5.00);
        try
        {
            System.out.println(label);//寫入前
            ObjectOutputStream out = new ObjectOutputStream(new
                FileOutputStream("Label.txt"));
            out.writeObject(label);
            out.close();
           
            System.out.println(label);//寫入後
           
            ObjectInputStream in = new ObjectInputStream(new
                FileInputStream("Label.txt"));
            LabeledPoint label1 = (LabeledPoint)in.readObject();
            in.close();
            System.out.println(label1);//讀出並加1.0後
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
   
}

class LabeledPoint implements Serializable
{
    public LabeledPoint(String str, double x, double y)
    {
        label = str;
        point = new Point2D.Double(x, y);
    }
   
    private void writeObject(ObjectOutputStream out) throws IOException
    {
        /**
         *必須通過調用defaultWriteObject()方法來寫入
         *對象的描述以及那些可以被序列化的字段
         */
        out.defaultWriteObject();
        out.writeDouble(point.getX());
        out.writeDouble(point.getY());
    }
   
    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
        /**
         *必須調用defaultReadObject()方法
         */
        in.defaultReadObject();
        double x = in.readDouble() + 1.0;
        double y = in.readDouble() + 1.0;
        point = new Point2D.Double(x, y);
    }
   
    public String toString()
    {
        return getClass().getName()
            + "[label = "+ label
            + ", point.getX() = "+ point.getX()
            + ", point.getY() = "+ point.getY()
            + "]";
    }
   
    private  String label;
    transient private Point2D.Double point;
}
 

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章