www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 公眾號精選 > 小林coding
[導讀]大家好,我是熱心網(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ù),那序列化的過程的二進制數(shù)據(jù)肯定是有某種固定的格式。

比如 JDK 自帶的序列化的過程如下圖:

序列化過程就是在讀取對象數(shù)據(jù)的時候,不斷加入一些特殊分隔符,這些特殊分隔符用于在反序列化過程中截斷用。

  • 頭部數(shù)據(jù)用來聲明序列化協(xié)議、序列化版本,用于高低版本向后兼容;

  • 對象數(shù)據(jù)主要包括類名、簽名、屬性名、屬性類型及屬性值,當然還有開頭結尾等數(shù)據(jù),除了屬性值屬于真正的對象值,其他都是為了反序列化用的元數(shù)據(jù)

  • 存在對象引用、繼承的情況下,就是遞歸遍歷“寫對象”邏輯

所以,從這個例子我們可以知道,任何一種序列化的方式,其核心思想就要設計一套將對象轉換成某種特定格式的二進制數(shù)據(jù),接著反序列化的時候,就能根據(jù)規(guī)則從二進制數(shù)據(jù)解析出對象。

這么看,序列化其實就是一種協(xié)議,序列化方和反序列化方都要遵循相同的規(guī)則,否則就無法得到正常的數(shù)據(jù)。

不過,JDK 原生序列化缺陷就是不能跨語言,只能在 Java 生態(tài)里使用。

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ū)十分活躍且更新速度很快。

我這里說個 C 的 JSON 第三方庫:JSON for Modern C 。

使用起來很方便,僅需要包含一個頭文件“json.hpp”,沒有外部依賴,也不需要額外的安裝、編譯、鏈接工作,適合快速上手開發(fā)。

因為 JSON 是 Key-Value 形式,所以 JSON for Modern C 的操作和標準容器 map 一樣,用關聯(lián)數(shù)組的“[]”來添加任意數(shù)據(jù)。

這里貼幾個例子:

//?給?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(
本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
關閉