Java序列化,看這篇就夠了
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,Google的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 是可以的