1. 程式人生 > >java中序列化和transient關鍵字使用小結

java中序列化和transient關鍵字使用小結

一、背景:Java中的物件序列化

1.物件序列化的類是有要求的,這個序列化的類必須實現一個介面Serializable,這個
介面沒有任何方法宣告,它是一個標識介面。

2.物件流可以將Java物件轉換成二進位制寫入磁碟,這個過程通常叫做序列化。
並且還可以從磁碟讀出完整的Java物件,而這個過程叫做反序列化。

3.序列化 ObjectOutputStream 把物件轉成檔案
反序列化 ObjectInputStream 把檔案轉成物件

4.序列化的意義
Java中物件的序列化指的是將物件轉換成以位元組序列的形式來表示,這些位元組序列包含了物件的資料和資訊,一個序列化後的物件可以被寫到資料庫或檔案中,也可用於網路傳輸,一般當我們使用快取cache(記憶體空間不夠有可能會本地儲存到硬碟)或遠端呼叫rpc(網路傳輸)的時候,經常需要讓我們的實體類實現Serializable介面,目的就是為了讓其可序列化。

當然,序列化後的最終目的是為了反序列化,恢復成原先的Java物件,要不然序列化後幹嘛呢,所以序列化後的位元組序列都是可以恢復成Java物件的,這個過程就是反序列化。

二、關於transient關鍵字

1.原理:
一個物件只要實現了Serilizable介面,這個物件就可以被序列化,java的這種序列化模式為開發者提供了很多便利,可以不必關係具體序列化的過程,只要這個類實現了Serilizable介面,這個的所有屬性和方法都會自動序列化。

但是有種情況是有些屬性是不需要序列化的,所以就用到transient這個關鍵字。只需要實現Serilizable介面,將不需要序列化的屬性前新增關鍵字transient,序列化物件的時候,這個屬性就不會序列化到指定的目的地中。

2.新增關鍵字transient,就是為了不被序列化。為什麼要不被序列化呢?
(1)為了資料安全,避免序列化和反序列化。在實際開發過程中,我們常常會遇到這樣的問題,這個類的有些屬性需要序列化,而其他屬性不需要被序列化,打個比方,如果一個使用者有一些敏感資訊(如密碼,銀行卡號等),為了安全起見,不希望在網路操作(主要涉及到序列化操作,本地序列化快取也適用)中被傳輸,這些資訊對應的變數就可以加上transient關鍵字。換句話說,這個欄位的生命週期僅存於呼叫者的記憶體中而不會寫到磁盤裡持久化。

(2)節省儲存空間。類中的欄位值可以根據其它欄位推匯出來,如一個長方形類有三個屬性:長度、寬度、面積。那麼在序列化的時候,面積這個屬性就沒必要被序列化了,因為沒有多大意義。

三、transient關鍵字使用例項

//import java.io.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class TransientTest{

public static void main(String args[]) throws Exception {

    //序列化之前的資料顯示
    Rectangle rectangle = new Rectangle(3,4);
    System.out.println("1.原始物件\n"+rectangle);

    //開始序列化
    try{
        ObjectOutputStream o = new ObjectOutputStream(new        
                           FileOutputStream("rectangle"));

        // 往流寫入物件,將Rectangle物件寫入rectangle檔案
        o.writeObject(rectangle);
        o.flush();
        o.close();
    }
    catch(FileNotFoundException e){
        //列印異常的堆疊資訊到控制檯
        e.printStackTrace();
    }
    catch(IOException e){
        e.printStackTrace();
    }

    //開始反序列化
    try{
        // 從流中讀取物件
        ObjectInputStream in = new ObjectInputStream(new 
                           FileInputStream("rectangle"));

        //從流中讀取Rectangle的資料
        Rectangle rectangle1 = (Rectangle)in.readObject();
        //輸出反序列化的結果
        System.out.println("2.反序列化後的物件\n"+rectangle1);

        rectangle1.setArea();
        System.out.println("3.恢復成原始物件\n"+rectangle1);
        in.close();
    }
    catch(ClassNotFoundException e){
        e.printStackTrace();
    }
    catch(FileNotFoundException e){
        e.printStackTrace();
    }
    catch(IOException e){
        e.printStackTrace();
    }
}

}

/**
*建立矩形類
*/
class Rectangle implements Serializable{

private static final long serialVersionUID = 1710022455003682613L;
private Integer width;
private Integer height;
//在反序列化時,將無法恢復原來值  
private transient Integer area;

//矩形函式Rectangle()
public Rectangle (Integer width, Integer height){
    this.width = width;
    this.height = height;
    this.area = width * height;
}

//計算矩形面積
public void setArea(){
    this.area = this.width * this.height;
}

@Override
public String toString(){

//StringBuffer是動態字串陣列 ,它的物件是可以擴充和修改
//Stringbuffer有append()方法 
StringBuffer sb = new StringBuffer(40);
//append()追加資訊,往動態字串陣列新增,類似於“+”
sb.append("width : ");
sb.append(this.width);
sb.append("\nheight : ");
sb.append(this.height);
sb.append("\narea : ");
sb.append(this.area);
return sb.toString();
}

}

輸出:
1.原始物件
width : 3
height : 4
area : 12
2.反序列化後的物件
width : 3
height : 4
area : null
3.恢復成原始物件
width : 3
height : 4
area : 12

四、序列化與反序列化常用語句

1.開始序列化
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(“rectangle”));
其實就是:FileOutputStream f = new FileOutputStream(“rectangle”);
ObjectOutputStream o = new ObjectOutputStream(f)

2.開始反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(“rectangle”));
其實就是:FileInputStream fi = new FileInputStream(“rectangle”);
ObjectInputStream in = new ObjectInputStream(fi)

五、小結

1.反序列化area為null,說明反序列化時根本沒有從檔案中獲取到資訊。

2.關於private static final long serialVersionUID = 1710022455003682613L;
serialVersionUID適用於Java的序列化機制。簡單來說,Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的位元組流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException。

3.當物件被序列化時,被transient關鍵字修飾的變數不會被序列化到目標檔案。
當物件從序列化檔案重構物件時(反序列化過程),被transient欄位修飾的變數不會被恢復原來值。一旦變數被transient修飾,變數將不再是物件持久化的一部分,該變數內容在序列化後無法獲得訪問。

4.transient關鍵字只能修飾變數,而不能修飾方法和類。注意,本地變數是不能被transient關鍵字修飾的。變數如果是使用者自定義類變數,則該類需要實現Serializable介面。

5.被transient關鍵字修飾的變數不再能被序列化。一個靜態變數不管是否被transient修飾,均不能被序列化,這裡與該變數被static修飾後,transient就失效了無關。

6.類變數加不加transient關鍵字都不會參與序列化和反序列化。
如:private transient static int minorVer;
private static transient final int sf = 110;

7.對於private transient InputStream is; 這個欄位如果用做序列化時,必須要加transient的,不然就會報錯(java.io.NotSerializableException),因為InputStream沒有實現可序列化的介面。加了後,這個欄位就不會進行序列化,就與InputStream沒有實現可序列化的介面不矛盾。