你不得不知道的對象的序列化和反序列化

對象的序列化和反序列化

序列化 (Serialization)將對象的狀態信息轉換爲能夠存儲或傳輸的形式的過程。在序列化期間,對象將其當前狀態寫入到臨時或持久性存儲區。之後,能夠經過從存儲區中讀取或反序列化對象的狀態,從新建立該對象。html

當你建立對象時,只要你須要,它就會一直存在,可是當程序終止的時候,那麼這個對象也就隨之消失了,儘管這麼作是有意義的,可是仍舊存在某些的狀況,若是對象可以在程序不運行的狀況下仍能存在而且保存其信息,那將會是很是有用的。這樣在下次運行程序的同事,該對象可以被重建而且擁有的信息與程序上次運行時它所擁有的信息相同。sql

簡單來講序列化和反序列化以下編程

  • 序列化:把對象轉換爲字節序列的過程稱爲對象的序列化
  • 反序列化:把字節序列恢復爲對象的過程稱爲對象的反序列化

而何時會用到序列化呢?通常在如下的狀況中會使用到序列化bash

  • 對象的持久化:把對象的字節序列永久地保存到硬盤上,一般存放在一個文件中

在不少應用中,須要對某些對象進行序列化,讓它們離開內存空間,入住物理硬盤,以便長期保存。好比最多見的是Web服務器中的Session對象,當有 10萬用戶併發訪問,就有可能出現10萬個Session對象,內存可能吃不消,因而Web容器就會把一些seesion先序列化到硬盤中,等要用了,再把保存在硬盤中的對象還原到內存中。服務器

  • 遠程調用:在網絡上傳送對象的字節序列

當兩個進程在進行遠程通訊時,彼此能夠發送各類類型的數據。不管是何種類型的數據,都會以二進制序列的形式在網絡上傳送。發送方須要把這個Java對象轉換爲字節序列,才能在網絡上傳送;接收方則須要把字節序列再恢復爲Java對象。網絡

序列化的基本實現

只要對象實現了Serializable接口,對象的序列化就會變得十分簡單。要序列化一個對象首先要建立某些OutputStream對象,而後將其封裝在一個ObejctOutputStream對象內,這時只須要調用writeObject()便可將對象序列化,並將其發送給OutputStream併發

對象序列化是基於字節的,因此要使用InputStreamOutputStream繼承層次結構app

若是要反向上面的過程(即將一個序列還原爲一個對象),須要將一個InputStream封裝在ObjectInputStream內,而後調用readObject(),和往常同樣,咱們最後得到是一個引用,它指向了一個向上轉型的Object,因此必須向下轉型才能直接設置它們。dom

對象序列化不只可以將實現了接口的那個類進行序列化,也可以將其引用的對象也實例化,以此類推。這種狀況能夠被稱之爲對象網。單個對象可與之創建鏈接。ide

下面咱們舉個例子能夠看到在序列化和反序列過程當中,對象網中的鏈接的對象信息都沒有變。

public class TestSerializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String fileName = "/Users/hupengfei/mytest.sql";
        Worm w = new Worm(6,'a');
        System.out.println("w:"+w);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName));
        out.writeObject("Worm Storage\n");
        out.writeObject(w);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName));
        String s = (String) in.readObject();
        Worm w2 = (Worm) in.readObject();
        System.out.println(s+"w2:"+w2);
    }
}

class Data implements Serializable{
    private Integer i ;
    public Data(Integer i ){
        this.i = i;
    }

    @Override
    public String toString() {
        return i.toString();
    }
}

class Worm implements Serializable{

    private static final long serialVersionUID = 8033549288339500180L;

    private static Random random = new Random(47);

    private Data [] d = {
            new Data(random.nextInt(10)),
            new Data(random.nextInt(10)),
            new Data(random.nextInt(10))
    };

    private Worm next;
    private char c;
    public Worm(int i ,char x){
        System.out.println("Worm Constructor:"+i);
        c = x;
        if (--i>0){
            next = new Worm(i,(char)(x+1));
        }
    }

    public Worm(){
        System.out.println("Default Constructor");
    }

    @Override
    public String toString() {
        StringBuffer result = new StringBuffer(":");
        result.append(c);
        result.append("(");
        for (Data data: d){
            result.append(data);
        }
        result.append(")");
        if (next!=null){
            result.append(next);
        }
        return result.toString();
    }
}

複製代碼

能夠看到打印信息以下

Worm Constructor:6
Worm Constructor:5
Worm Constructor:4
Worm Constructor:3
Worm Constructor:2
Worm Constructor:1
w::a(853):b(119):c(802):d(788):e(199):f(881)
Worm Storage
w2::a(853):b(119):c(802):d(788):e(199):f(881)

複製代碼

在生成Data對象時是用隨機數初始化的,從輸出中能夠看出,被還原後的對象確實包含了原對象中的全部連接。

上面咱們舉了個如何進行序列化的例子,其中或許看到了serialVersionUID這個字段,若是不加的話,那麼系統會自動的生成一個,而若是修改了類的話,哪怕加一個空格那麼這個serialVersionUID也會改變,那麼在反序列化的時候就會報錯,由於在反序列化的時候會將serialVersionUID和以前的serialVersionUID進行對比,只有相同的時候纔會反序列化成功。因此仍是建議顯視的定義一個serialVersionUID

transient(瞬時)關鍵字

當咱們在對序列化進行控制的時候,可能須要某個字段不想讓Java進行序列化機制進行保存其信息與恢復。若是一個對象的字段保存了咱們不但願將其序列化的敏感信息(例如密碼)。儘管咱們使用private關鍵字可是若是通過序列化,那麼在進行反序列化的時候也是能將信息給恢復過來的。咱們舉個例子以下:

咱們定義個Student

class Student implements Serializable{
    private static final long serialVersionUID = 1734284264262085307L;
    private String password;
------get set 方法
}

複製代碼

而後將其序列化到文件中而後再從文件中反序列化

public static void main(String[] args) throws IOException, ClassNotFoundException {
    String fileName="/Users/hupengfei/mytest.sql";
    Student student = new Student();
    student.setPassword("123456");
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(fileName));
    objectOutputStream.writeObject(student);
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileName));
    Student readStudent = (Student) objectInputStream.readObject();
    System.out.println(readStudent.getPassword());
}
複製代碼

而後發現輸出爲

readStudent的password=123456

複製代碼

此時咱們若是想password參數在序列化的時候存儲其值,那麼能夠加上transient關鍵字,就像下面同樣

private transient String password;

複製代碼

而後輸出以下

readStudent的password=null

複製代碼

發如今序列化的時候參數就已經沒被保存進去了

參考文章

相關文章
相關標籤/搜索