java序列化,看這篇就夠了

說故事的五公子發表於2021-01-29

1.什麼是序列化

Java序列化是指把Java物件轉換為位元組序列的過程,而Java反序列化是指把位元組序列恢復為Java物件的過程:

  • 序列化:物件序列化的最主要的用處就是在傳遞和儲存物件的時候,保證物件的完整性和可傳遞性。序列化是把物件轉換成有序位元組流,以便在網路上傳輸或者儲存在本地檔案中。核心作用是物件狀態的儲存與重建。
  • 反序列化:客戶端從檔案中或網路上獲得序列化後的物件位元組流,根據位元組流中所儲存的物件狀態及描述資訊,通過反序列化重建物件。

2.序列化優點

一:物件序列化可以實現分散式物件。

主要應用例如:RMI(即遠端呼叫Remote Method Invocation)要利用物件序列化執行遠端主機上的服務,就像在本地機上執行物件時一樣。

二:java物件序列化不僅保留一個物件的資料,而且遞迴儲存物件引用的每個物件的資料。

可以將整個物件層次寫入位元組流中,可以儲存在檔案中或在網路連線上傳遞。利用物件序列化可以進行物件的"深複製",即複製物件本身及引用的物件本身。序列化一個物件可能得到整個物件序列。

三:序列化可以將記憶體中的類寫入檔案或資料庫中。

比如:將某個類序列化後存為檔案,下次讀取時只需將檔案中的資料反序列化就可以將原先的類還原到記憶體中。也可以將類序列化為流資料進行傳輸。

總的來說就是將一個已經例項化的類轉成檔案儲存,下次需要例項化的時候只要反序列化即可將類例項化到記憶體中並保留序列化時類中的所有變數和狀態。

四:物件、檔案、資料,有許多不同的格式,很難統一傳輸和儲存。

序列化以後就都是位元組流了,無論原來是什麼東西,都能變成一樣的東西,就可以進行通用的格式傳輸或儲存,傳輸結束以後,要再次使用,就進行反序列化還原,這樣物件還是物件,檔案還是檔案。

什麼場景下會用到序列化

  • 暫存大物件

  • 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.常見的序列化技術

1、java 序列化

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

  缺點:不支援跨語言處理、效能相對不是很好,序列化以後產生的資料相對較大

2、XML序列化

  XML序列化的好處在於可讀性好,方便閱讀和除錯。但是序列化以後的 位元組碼檔案比較大,而且效率不高,適應於對效能不高,而且QPS較低的企業級內部系統之間的資料交換的場景,同時XML又具有語言無關性,所以還可以用於異構系統之間的資料交換和協議。比如我們熟知的WebService,就是採用XML格式對資料進行序列化的

3、JSON序列化

  JSON(JavaScript Object Notation)是一種輕量級的資料交換格式,相對於XML來說,JON的位元組流較小,而且可讀性也非常好。現在JSON資料格式的其他運用最普遍的。序列化方式還衍生了阿里的fastjson,美團的MSON,谷歌的GSON等更加優秀的轉碼工具。

4、Hessian 序列化框架子

  Hessian是一個支援跨語言傳輸的二進位制序列化協議,相對於Java預設的序列化機制來說,Hessian具有更好的效能和易用性,而且支援多重不同的語言,實際上Dubbo採用的就是Hessian序列化來實現,只不過Dubbo對Hessian進行重構,效能更高。

5、Protobuf 序列化框架

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

  Google 提供了多種語言來實現,比如 Java、C、Go、Python,每一種實現都包含了相應語言的編譯器和庫檔案Protobuf 使用比較廣泛,主要是空間開銷小和效能比較好,非常適合用於公司內部對效能要求高的 RPC 呼叫。 另外由於解析效能比較高,序列化以後資料量相對較少,所以也可以應用在物件的持久化場景中但是但是要使用 Protobuf 會相對來說麻煩些,因為他有自己的語法,有自己的編譯器。

選型建議

  ① 對效能要求不高的場景,可以採用基於 XML 的 SOAP 協議

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

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

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

相關文章