攤牌了,我是熱心網(wǎng)友!
時間:2021-12-07 15:43:18
手機看文章
掃描二維碼
隨時隨地手機看文章
[導讀]大家好,我是熱心網(wǎng)友?——小林。有位讀者問了,我這么一個問題:不管是RPC或者HTTP,只要傳輸?shù)膬热菔恰笇ο蟆?,要想在接收方還原出一摸一樣的「對象」,那就需要序列化和反序列化。那什么是序列化和反序列化呢?RPC能幫助我們的應用透明地完成遠程調用,即調用其他服務器的函數(shù)就像調用本...
大家好,我是熱心網(wǎng)友?—— 小林。有位讀者問了,我這么一個問題:不管是 RPC 或者 HTTP,只要傳輸?shù)膬热菔恰笇ο蟆?,要想在接收方還原出一摸一樣的「對象」,那就需要序列化和反序列化。
那什么是序列化和反序列化呢?RPC 能幫助我們的應用透明地完成遠程調用,即調用其他服務器的函數(shù)就像調用本地方法一樣。發(fā)起調用請求的那一方叫做調用方,被調用的一方叫做服務提供方。調用方和服務提供方一般是不同的服務器,所以就需要通過網(wǎng)絡來傳輸數(shù)據(jù),并且 RPC 常用于業(yè)務系統(tǒng)之間的數(shù)據(jù)交互,需要保證其可靠性,所以 RPC 一般默認采用 TCP 協(xié)議來傳輸。同時, HTTP 協(xié)議也是建立在 TCP 之上的。網(wǎng)絡傳輸?shù)臄?shù)據(jù)必須是二進制數(shù)據(jù),但調用方請求的出入?yún)?shù)都是對象,而對象是肯定沒法直接在網(wǎng)絡中傳輸?shù)?,需要提前把「對象轉成二進制數(shù)據(jù)」進行網(wǎng)絡傳輸,這個轉換過程就做序列化。相反,服務提供方收到網(wǎng)絡數(shù)據(jù)后,需要將「二進制數(shù)據(jù)轉成對象」,這個轉換過程就叫做反序列化。總結來說,序列化就是將對象轉換成二進制數(shù)據(jù)的過程,以方便傳輸或存儲。而反序列就是將二進制轉換為對象的過程。
為什么 RPC 經常提到序列化呢?因為網(wǎng)絡傳輸?shù)臄?shù)據(jù)必須是二進制數(shù)據(jù),所以在 RPC 調用中,對入?yún)ο笈c返回值對象進行序列化與反序列化是一個必須的過程。
HTTP 什么時候需要序列化呢?舉個例子。當客戶端和服務端交互的數(shù)據(jù)是 JSON,這時候發(fā)送方需要將 JSON 對象轉換成二進制數(shù)據(jù)發(fā)送到網(wǎng)絡,接收方需要將接收到的二進制數(shù)據(jù)轉換成 JSON 對象。說了,這么多概念,接下來跟大家說說有哪些常用的序列化方式?
JDK 原生序列化
Java 語言中 JDK 就自帶有序列化的方式,舉個 JDK 序列化的例子。注意,這個例子是將 Student 對象保存到文件,保存到文件的數(shù)據(jù)是二進制數(shù)據(jù),所以并不是說序列化只用在網(wǎng)絡傳輸場景里,只要是保存數(shù)據(jù)的場景只能是二進制數(shù)據(jù)時,就需要用序列化。import?java.io.*;
public?class?Student?implements?Serializable?{
????//學號
????private?int?no;
????//姓名
????private?String?name;
????public?int?getNo()?{
????????return?no;
????}
????public?void?setNo(int?no)?{
????????this.no?=?no;
????}
????public?String?getName()?{
????????return?name;
????}
????public?void?setName(String?name)?{
????????this.name?=?name;
????}
????@Override
????public?String?toString()?{
????????return?"Student{"?
????????????????"no="? ?no?
????????????????",?name='"? ?name? ?'\''?
????????????????'}';
????}
????public?static?void?main(String[]?args)?throws?IOException,?ClassNotFoundException?{
???????
????????String?basePath?=?"/root";
????????FileOutputStream?fos?=?new?FileOutputStream(basePath? ?"student.dat");
????????
????????//初始化一個學生對象
????????Student?student?=?new?Student();
????????student.setNo(1);
????????student.setName("xiaolin");
????????
????????//將學生對象序列化到文件里
????????ObjectOutputStream?oos?=?new?ObjectOutputStream(fos);
????????oos.writeObject(student);
????????oos.flush();
????????oos.close();
????????//讀取文件中的二進制數(shù)據(jù),并將數(shù)據(jù)反序列化為學生對象
????????FileInputStream?fis?=?new?FileInputStream(basePath? ?"student.dat");
????????ObjectInputStream?ois?=?new?ObjectInputStream(fis);
????????Student?deStudent?=?(Student)?ois.readObject();
????????ois.close();
????????System.out.println(deStudent);
????}
}
從上面的代碼,我們可以知道:- JDK 自帶的序列化具體的實現(xiàn)是由 ObjectOutputStream 完成的;
- 而反序列化的具體實現(xiàn)是由 ObjectInputStream 完成的。
- 頭部數(shù)據(jù)用來聲明序列化協(xié)議、序列化版本,用于高低版本向后兼容;
- 對象數(shù)據(jù)主要包括類名、簽名、屬性名、屬性類型及屬性值,當然還有開頭結尾等數(shù)據(jù),除了屬性值屬于真正的對象值,其他都是為了反序列化用的元數(shù)據(jù)
- 存在對象引用、繼承的情況下,就是遞歸遍歷“寫對象”邏輯
JSON
JSON 數(shù)據(jù)的格式相信大家都很熟悉了吧,在 Web 應用里特別常見,通常后端和前端的耦合就是 JSON 數(shù)據(jù)。JSON 是典型的 Key-Value 方式,沒有數(shù)據(jù)類型。很多語言都實現(xiàn)了 JSON 序列化的第三庫,所以 JSON 序列化的方式可以跨語言。比如,Java 語言中常用的 JSON 第三方類庫有:- Gson: 谷歌開發(fā)的 JSON 庫,功能十分全面。
- FastJson: 阿里巴巴開發(fā)的 JSON 庫,性能十分優(yōu)秀。
- Jackson: 社區(qū)十分活躍且更新速度很快。
//?給?JSON?for?Modern?C ?的?json?類取個別名
using?json_t?=?nlohmann::json;??
//?JSON對象
json_t?j;???????????????????????????????????
//?"age":18
j["age"]?=?18;
//?"name":"xiaolin"
j["name"]?=?"xiaolin";????
//?"gear":{"suits":"2099"}
j["gear"]["suits"]?=?"2099";?
//?"jobs":["superhero"]??
j["jobs"]?=?{"superhero"};??????????????????
vector<int>?v?=?{1,2,3};??
//?"numbers":[1,2,3]
j["numbers"]?=?v;??????????????????????????
map<string,?int>?m?=???????????????????????
????{{"one",?1},?{"two",?2}};????
//?"kv":{"one":1,"two":2}
j["kv"]?=?m;???????????????????????????????
添加完 JSON 數(shù)據(jù)后,就可以調用成員函數(shù) dump() 進行初始化,得到 JSON 文本形式,也就是 JSON 字符串:cout?<endl;
反序列化也很簡單,只要調用靜態(tài)成員函數(shù) parse() 就行,直接得到 JSON 對象://?JSON文本,原始字符串
string?jsonStr?=?R"({???????????????
????"name":?"xiaolin",
????"age"?:?18
})";
//?從字符串反序列化
json_t?j?=?json_t::parse(jsonStr);????
//?驗證序列化是否正確
assert(j["age"]?==?18);????????
assert(j["name"]?==?"xiaolin");
對于通常的應用來說,掌握了基本的序列化和反序列化就夠用了,如果想要了解 JSON for Modern C 其他特性,可以去看它的 Github。JSON 進行序列化存在的問題,因為 JSON 進行序列化的額外空間開銷比較大,對于大數(shù)據(jù)量服務這意味著需要巨大的內存和磁盤開銷,所以如果在傳輸數(shù)據(jù)量比較小的場景,就可以采用 JSON 序列化的方式。ProtoBuffer
ProtoBuf 是由 Google 出品的,是一種輕便、高效的結構化數(shù)據(jù)存儲格式,可以用于結構化數(shù)據(jù)序列化,支持 Java、Python、C 、Go 等語言。Protobuf 使用的時候必須寫一個 IDL(Interface description language)文件,在里面定義好數(shù)據(jù)結構,只有預先定義了的數(shù)據(jù)結構,才能被序列化和反序列化。下面是一個簡單的 IDl 文件格式:syntax = "proto2"; // 使用第2版
package sample; // 定義名字空間
message Person { // 定義消息
required string name = 1; // required表示必須字段
required int32 id = 2;
optional string email = 3; // optional字段可以沒有
}
寫完 IDL 文件后,然后使用不同語言的 IDL 編譯器,生成序列化工具類。Protobuf 在 Github 有文檔介紹了不同語言是怎么使用編譯器生成序列化工具類了,我在這里就不介紹了。這里貼 C 和 Java 語言使用 Protobuf 相關接口進行序列化和反序列化的例子。C 進行序列化和反序列化的例子://?類型別名
using?person_t?=?sample::Person;????????
//?聲明一個Protobuf對象
person_t?p;?????????????????????????????
//?設置每個字段的值?
p.set_id(1);???????????????????????????
p.set_name("xiaolin");
p.set_email("xiaolincoding@163.com");
//?序列化到字符串?
string?enc;
p.SerializeToString(