深入分析java序列化

來源:互聯網
上載者:User

標籤:序列化   serializa   java   json   還原序列化   

概念

先來點簡單的概念:
what?why?
什麼是序列化?為什麼要序列化?
答曰:將java對象轉成位元組序列,用以傳輸和儲存
where?
使用情境是什嗎?
答曰:對象的傳輸;狀態的備份,例如jvm的dump檔案;
好了,不裝*了,下面說的詳細點。其實對象的序列化主要有兩種用途:

  • 把對象的位元組序列永久地儲存到硬碟上,通常存放在一個檔案中
  • 在網路上傳送對象的位元組序列

在很多應用中,需要對某些對象進行序列化,讓它們離開記憶體空間,入住物理硬碟,以便長期儲存。比如最常見的是Web伺服器中的Session對象,當有 10萬使用者並發訪問,就有可能出現10萬個Session對象,記憶體可能吃不消,於是Web容器就會把一些seesion先序列化到硬碟中,等要用了,再把儲存在硬碟中的對象還原到記憶體中。
當兩個進程在進行遠程通訊時,彼此可以發送各種類型的資料。無論是何種類型的資料,都會以二進位序列的形式在網路上傳送。發送方需要把這個Java對象轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢複為Java對象。

實現方式

how?

  1. (需要序列化的類)實現Serializable介面。查看源碼可知Serializable是個空介面(裡面沒有任何方法),即標記介面。作用就是明確地告訴java,這個類需要序列化,不實現這個介面,那這個對象是沒法序列化和傳輸的(如果不加implements Serializable,會報錯,建議試試,直觀感受下),類似的標記介面還有cloneable。
  2. 建立輸入輸出資料流。首先,序列化一個對象,需要要建立某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然後將這些OutputStream封裝在一個ObjectOutputStream中。這時候,只需要調用writeObject()方法就可以將對象序列化,並將其發送給OutputStream(對象的序列化是基於位元組的,不能使用Reader和Writer等基於字元的階層)。其次,反序列的過程(即將一個序列還原成為一個對象),則需要將一個InputStream(如FileInputstream、ByteArrayInputStream等)封裝在ObjectInputStream內,然後調用readObject()即可。簡單來說即:
    序列化:ObjectOutputStream.writeObject(Object)
    還原序列化:ObjectInputStream.readObject()

見下面的例子:
先定義一個待序列化的對象:

package com.alibaba.serialize.common;import java.io.Serializable;/* * 不加implements Serializable,會報錯 */public class User implements Serializable{    private static final long serialVersionUID = 1L;    private String username;    private int age;    public User(String username, int age) {        this.username = username;        this.age = age;    }    public String getUsername() {        return username;    }    public void setUsername(String username) {        this.username = username;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }}

再寫一個序列化的例子

package com.alibaba.serialize.common;import java.io.BufferedOutputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class SerializeExample {    public static final String out_file = "src/com/alibaba/serialize/temp.out";    public static void main(String[] args) {        User user = new User("tony", 18);        try {            //如果這裡改成網路輸出資料流而不是檔案輸出資料流(還原序列化那裡也同樣改成網路輸入資料流),則可以在網路上傳輸            ObjectOutputStream out = new ObjectOutputStream(                               new BufferedOutputStream(new FileOutputStream(out_file)));            out.writeObject("使用者資訊..");            out.writeObject(user);            out.close();        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }        System.out.println("序列化完成。請調用還原序列化類DeserializeExample完成還原序列化!");    }}

最後再寫一個還原序列化的例子

package com.alibaba.serialize.common;import java.io.BufferedInputStream;import java.io.FileInputStream;import java.io.ObjectInputStream;public class DeserializeExample {    public static void main(String[] args) throws Exception{            ObjectInputStream in = new ObjectInputStream(                               new BufferedInputStream(new FileInputStream(SerializeExample.out_file)));            String title = (String) in.readObject();            System.out.println(title);            User user = (User) in.readObject();            System.out.println("使用者姓名:"+user.getUsername());            System.out.println("使用者年齡:"+user.getAge());    }}

分別運行SerializeExampleDeserializeExample 可以看到對應的效果。

序列化的檔案關係

說幾點平常不注意容易忽略的地方。

  1. 如果一個類沒有實現Serializable介面,但是它的基類實現了,那麼這個類也是可以序列化的;
  2. 相反,如果一個類實現了Serializable介面,但是它的父類沒有實現,那麼這個類還是可以序列化(Object是所有類的父類),但是序列化該子類對象,然後還原序列化後輸出父類定義的某變數的數值,會發現該變數數值與序列化時的數值不同(一般為null或者其他預設值),而且這個父類裡面必須有無參的構造方法,不然子類還原序列化的時候會報錯。

見樣本:
先寫個父類

package com.alibaba.serialize.parent;import java.io.Serializable;public class CarSerialize{    private String name;    private Long price;    /*     * 沒有這個無參建構函式會報錯,可以刪除,測試下     */    public CarSerialize() {        super();    }    public CarSerialize(String name, Long price) {        this.name = name;        this.price = price;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Long getPrice() {        return price;    }    public void setPrice(Long price) {        this.price = price;    }}

再寫個子類

package com.alibaba.serialize.parent;import java.io.Serializable;public class BMWSerialize extends CarSerialize implements Serializable{    private String name;    private Long price;    public BMWSerialize(String name, Long price) {        super(name, price);    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Long getPrice() {        return price;    }    public void setPrice(Long price) {        this.price = price;    }   }

最後寫個測試類別

package com.alibaba.serialize.parent;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class SerializeTest {    public static void main(String[] args) {        try {            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj"));            out.writeObject(new BMWSerialize("BMW",100L));            out.close();            ObjectInputStream oin = new ObjectInputStream(new FileInputStream("result.obj"));            BMWSerialize bmw = (BMWSerialize) oin.readObject();            System.out.println("解密後的字串:" + bmw.getName());            oin.close();        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }    }}

上述代碼中,大家可以嘗試按照我上面說的兩點,分情況進行測試。

這裡還有個需要注意的地方
同一個流的對象參考關聯性被很好地保留了下來,不同流的對象參考關聯性則無法保證匹配
這一點我感覺比較容易理解,就不寫了,有時間可以測試下。

序列化ID

思考一個問題:如果序列化之後,兩端或者版本不同,class不一致怎麼辦?

???
???

這裡就有序列化ID的概念了,serialVersionUID適用於JAVA的序列化機制。還原序列化時,如果類發生了變動,則構造器和指派陳述式不會生效。

簡單來說,Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。在進行還原序列化時,JVM會把傳來的位元組流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行還原序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException(如同上面一節,你把父類的無參構造方法刪除之後,再還原序列化也會報這個錯)。

serialVersionUID有兩種顯示的產生方式:

  • 一是預設的1L,比如:private static final long serialVersionUID = 1L
  • 二是根據類名、介面名、成員方法及屬性等來產生一個64位的雜湊欄位,比如:private static final long serialVersionUID = xxxxL;

再思考一個問題,如果序列化ID是一樣的,假設A端是序列化,B端是還原序列化,還原序列化之前B端序列化對象發生變化,會有幾種情況?其實邏輯還是比較容易理解的,略微總結了下,大致如下:

  • B加欄位:序列化,還原序列化正常,B端新增加的int欄位被賦予了欄位類型的預設值(如0或者false)
  • B刪欄位:序列化,還原序列化正常(不會報錯),B端欄位少於A端,A端多的欄位值丟失
自訂序列化

既然題目是深入分析,那當然要接著分析,哈哈~所以,再問一個問題,當對象中有敏感欄位怎麼辦?如password。是不是自己可以決定序列化方式呢?這裡解釋一下

在序列化過程中,虛擬機器會試圖調用對象類裡的 writeObject 和 readObject 方法,進行使用者自訂的序列化和還原序列化,如果沒有這樣的方法,則預設調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。使用者自訂的 writeObject 和 readObject 方法可以允許使用者控制序列化的過程,比如可以在序列化的過程中動態改變序列化的數值。

基於這個原理,可以在實際應用中得到使用,用于敏感欄位的加密工作,見樣本(代碼摘自參考資料)。
先寫一個自訂序列化過程的待序列化對象。

package com.alibaba.serialize.customer;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectInputStream.GetField;import java.io.ObjectOutputStream;import java.io.ObjectOutputStream.PutField;import java.io.Serializable;public class SecurityInfo implements Serializable{    private String password = "pass";    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }    private void writeObject(ObjectOutputStream out) {        try {            PutField putFields = out.putFields();            System.out.println("原密碼:" + password);            password = "encryption";//類比加密            putFields.put("password", password);            System.out.println("加密後的密碼" + password);            out.writeFields();        } catch (IOException e) {            e.printStackTrace();        }    }    private void readObject(ObjectInputStream in) {        try {            GetField readFields = in.readFields();            Object object = readFields.get("password", "");            System.out.println("要解密的字串:" + object.toString());            password = "pass";//類比解密,需要獲得本地的密鑰        } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }    }}

再寫一個測試類別

package com.alibaba.serialize.customer;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class CustomerSerialize {    public static void main(String[] args) {        try {            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj"));            out.writeObject(new SecurityInfo());            out.close();            ObjectInputStream oin = new ObjectInputStream(new FileInputStream("result.obj"));            SecurityInfo t = (SecurityInfo) oin.readObject();            System.out.println("解密後的字串:" + t.getPassword());            oin.close();        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }    }}

ok!運行下看看吧。
其實說到自訂序列化,還可以通過實現java序列化另一個介面Externalizable,實現Externalizable介面就需要實現它的兩個方法,如下:

大致總結了下,Externalizable和Serializable的區別:

實現Serializable介面 實現Externalizable
系統自動儲存必要資訊 程式員決定儲存哪些資訊
Java內建支援,易於實現,只需實現該介面如何即可,無須任何代碼支援 僅僅提供兩個空方法,實現該介面必須為兩個空方法提供實現
效能略差 效能略高

其實雖然Externalizable效能高,但是我們一般很少在代碼裡看到,這個原因需要請假下別人,個人認為可能是自己寫不夠通用,太麻煩,而且一般敏感資訊也不會這麼傳。

幾種序列化協議比較

這裡主要介紹和對比幾種當下比較流行的序列化協議,包括XML、JSON、Protobuf、Thrift和Avro。這裡因為我用的也不多,只用過json和xml,所以,對比的話這裡列了個參考資料: http://tech.meituan.com/serialization_vs_deserialization.html

本文還參考:
http://blog.csdn.net/zhaozheng7758/article/details/7820018

感謝上述作者~

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

深入分析java序列化

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.