Java序列化,看這篇就夠了

1.什麼是序列化

Java序列化是指把Java對象轉換爲字節序列的過程,而Java反序列化是指把字節序列恢復爲Java對象的過程:java

  • 序列化:對象序列化的最主要的用處就是在傳遞和保存對象的時候,保證對象的完整性和可傳遞性。序列化是把對象轉換成有序字節流,以便在網絡上傳輸或者保存在本地文件中。核心做用是對象狀態的保存與重建。
  • 反序列化:客戶端從文件中或網絡上得到序列化後的對象字節流,根據字節流中所保存的對象狀態及描述信息,經過反序列化重建對象。

2.序列化優勢

一:對象序列化能夠實現分佈式對象。算法

主要應用例如:RMI(即遠程調用Remote Method Invocation)要利用對象序列化運行遠程主機上的服務,就像在本地機上運行對象時同樣。數據庫

二:java對象序列化不只保留一個對象的數據,並且遞歸保存對象引用的每一個對象的數據。json

能夠將整個對象層次寫入字節流中,能夠保存在文件中或在網絡鏈接上傳遞。利用對象序列化能夠進行對象的"深複製",即複製對象自己及引用的對象自己。序列化一個對象可能獲得整個對象序列。後端

三:序列化能夠將內存中的類寫入文件或數據庫中。api

好比:將某個類序列化後存爲文件,下次讀取時只需將文件中的數據反序列化就能夠將原先的類還原到內存中。也能夠將類序列化爲流數據進行傳輸。安全

總的來講就是將一個已經實例化的類轉成文件存儲,下次須要實例化的時候只要反序列化便可將類實例化到內存中並保留序列化時類中的全部變量和狀態。網絡

四:對象、文件、數據,有許多不一樣的格式,很難統一傳輸和保存。框架

序列化之後就都是字節流了,不管原來是什麼東西,都能變成同樣的東西,就能夠進行通用的格式傳輸或保存,傳輸結束之後,要再次使用,就進行反序列化還原,這樣對象仍是對象,文件仍是文件。前後端分離

什麼場景下會用到序列化

  • 暫存大對象

  • Java對象須要持久化的時候

  • 須要在網絡,例如socket中傳輸Java對象。由於數據只可以以二進制的形式在網絡中進行傳輸,所以當把對象經過網絡發送出去以前須要先序列化成二進制數據,在接收 端讀到二進制數據以後反序列化成Java對象

  • 深度克隆(複製)

  • 跨虛擬機通訊

3.如何使用序列化

經過上面的介紹你們已經瞭解了什麼是序列化,以及爲何要使用序列化。這一節咱們一塊兒來學習一下如何使用序列化。

首先咱們要把準備要序列化類,實現 Serializabel接口,至於爲何要實現Serializabel接口,咱們後面再詳細介紹。

咱們如今想要將Person序列化,Person類以下:

package com.wugongzi.day0112;

import java.io.Serializable;

/**
 * add by wugongzi 2021/1/22
 */
public class Person implements Serializable {
    private int id;
    private String name;
    private int age;


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

序列化:

package com.wugongzi.day0112;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 序列化Person
 * add by wugongzi 2021/1/22
 */
public class SerializationTest {
    public static void main(String[] args) throws IOException {
        Person p1 = new Person(1, "jack", 19);
        Person p2 = new Person(2, "mary", 22);
        List<Person> list = new ArrayList();
        list.add(p1);
        list.add(p2);

        // 建立文件流
        FileOutputStream fos = new FileOutputStream("/Users/File/person.txt");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(list);
        os.close();
        System.out.println("serialization  success");
    }
}

這裏的person.txt就是序列化到本地的文件(打開會是亂碼)

反序列化:

package com.wugongzi.day0112;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 反序列化Person
 * add by wugongzi 2021/1/22
 */
public class DeserializationTest {

    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("/Users/File/person.txt");
        ObjectInputStream is = new ObjectInputStream(fis);
        Object obj = null;
        List<Person> list = new ArrayList<>();
        try {
            list = (List<Person>)is.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        is.close();

        //遍歷list,輸出
        for (Person person:list){
            System.out.println(person.toString());
        }

    }

}

輸出結果:

Person{id=1, name='jack', age=19}
Person{id=2, name='mary', age=22}

序列化流程:

 1)定義一個類,實現Serializable接口;

 2)在程序代碼中建立對象後,建立對象輸出流ObjectOutputStream對象並在構造參數中指定流的輸出目標(好比一個文件),經過objectOutputStream.writeObject(obj)把對象序列化並輸出到流目標處;

 3)在須要提取對象處:建立對象輸入流ObjectInputStream對象並在構造參數中指定流的來源,而後經過readObject()方法獲取對象,並經過強制類型轉換賦值給類對象引用。

4.序列化原理

序列化算法會按步驟執行如下事情:

  1)當前類描述的元數據輸出爲字節序列;【類定義描述、類中屬性定義描述】

  2)超類描述輸出爲字節序列;【若是超類還有超類,則依次遞歸,直至沒有超類】

  3)從最頂層超類往下,依次輸出各種屬性值描述,直至當前類對象屬性值。

  即:從下到上描述類定義,從上往下輸出屬性值。

5.爲何Java序列化要實現Serializable

首先咱們來看一下Serializable接口源碼:

package java.io;
public interface Serializable {
}

對,你沒有看錯,就是一個空接口。

既然這個接口裏面什麼東西都沒有,那麼實現這個接口意義何在呢?讀到這裏或許有不少同窗會產生疑問:

一個空接口,裏面啥都沒有。爲何java設計的時候必定要實現Serializable才能序列化?不能去掉Serializable這個接口,讓每一個對象都能序列化嗎?

比較有說服力的解釋是:

總的就是說安全性問題,假如沒有一個接口(即沒有Serializable來標記是否能夠序列化),讓全部對象均可以序列化。那麼全部對象經過序列化存儲到硬盤上後,均可以在序列化獲得的文件中看到屬性對應的值(後面將會經過代碼展現)。因此最後爲了安全性(即不讓一些對象中私有屬性的值被外露),不能讓全部對象均可以序列化。要讓用戶本身來選擇是否能夠序列化,所以須要一個接口來標記該類是否可序列化。

6.幾個須要注意的點

1)靜態變量和transient關鍵字修飾的變量不能被序列化;

​ 序列化時並不保存靜態變量,這其實比較容易理解,序列化保存的是對象的狀態,靜態變量屬於類的狀態,所以 序列化並不保存靜態變量。transient做用是控制變量的序列化,在變量聲明前加上該關鍵字,能夠阻止該變量被序列化到文件中,在被反序列化後,transient變量的值設爲初始值,如int型的是0。

2)反序列化時要按照序列化的順序重構對象:如先序列化A後序列化B,則反序列化時也要先獲取A後獲取B,不然報錯。

3)serialVersionUID(序列化ID)的做用:決定着是否可以成功反序列化。

  虛擬機是否容許對象反序列化,不是取決於該對象所屬類路徑和功能代碼是否與虛擬機加載的類一致,而是主要取決於對象所屬類與虛擬機加載的該類的序列化 ID 是否一致

  java的序列化機制是經過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地實體類中的serialVersionUID進行比較,若是相同則認爲是一致的,即可以進行反序列化,不然就會報序列化版本不一致的異常。

4)自定義序列化方法的應用場景:對某些敏感數據進行加密操做後再序列化;反序列化對加密數據進行解密操做。

5)重複序列化:同一個對象重複序列化時,不會把對象內容再次序列化,而是新增一個引用指向第一次序列化時的對象而已。

6)序列化實現深克隆:在java中存在一個Cloneable接口,經過實現這個接口的類都會具有clone的能力,同時clone在內存中進行,在性能方面會比咱們直接經過new生成對象要高一些,特別是一些大的對象的生成,性能提高相對比較明顯。

7.常見的序列化技術

一、java 序列化

  優勢:java語言本省提供,使用比較方面和簡單

  缺點:不支持跨語言處理、性能相對不是很好,序列化之後產生的數據相對較大

二、XML序列化

  XML序列化的好處在於可讀性好,方便閱讀和調試。可是序列化之後的 字節碼文件比較大,並且效率不高,適應於對性能不高,並且QPS較低的企業級內部系統之間的數據交換的場景,同時XML又具備語言無關性,因此還能夠用於異構系統之間的數據交換和協議。好比咱們熟知的WebService,就是採用XML格式對數據進行序列化的

三、JSON序列化

  JSON(JavaScript Object Notation)是一種輕量級的數據交換格式,相對於XML來講,JON的字節流較小,並且可讀性也很是好。如今JSON數據格式的其餘運用最廣泛的。序列化方式還衍生了阿里的fastjson,美團的MSON,谷歌的GSON等更加優秀的轉碼工具。

四、Hessian 序列化框架子

  Hessian是一個支持跨語言傳輸的二進制序列化協議,相對於Java默認的序列化機制來講,Hessian具備更好的性能和易用性,並且支持多重不一樣的語言,實際上Dubbo採用的就是Hessian序列化來實現,只不過Dubbo對Hessian進行重構,性能更高。

五、Protobuf 序列化框架

  Protobuf是Google的一種數據交換格式,它獨立於語言、獨立於平臺。

  Google 提供了多種語言來實現,好比 Java、C、Go、Python,每一種實現都包含了相應語言的編譯器和庫文件Protobuf 使用比較普遍,主要是空間開銷小和性能比較好,很是適合用於公司內部對性能要求高的 RPC 調用。 另外因爲解析性能比較高,序列化之後數據量相對較少,因此也能夠應用在對象的持久化場景中可是可是要使用 Protobuf 會相對來講麻煩些,由於他有本身的語法,有本身的編譯器。

選型建議

  ① 對性能要求不高的場景,能夠採用基於 XML 的 SOAP 協議

  ② 對性能和間接性有比較高要求的場景,那麼Hessian、Protobuf、Thrift、Avro 均可以

  ③ 基於先後端分離,或者獨立的對外的 api 服務,選用 JSON 是比較好的,對於調試、可讀性都很不錯

  ④ Avro 設計理念偏於動態類型語言,那麼這類的場景使用 Avro 是能夠的

相關文章
相關標籤/搜索