<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          攤牌了,我是熱心網(wǎng)友!

          共 3757字,需瀏覽 8分鐘

           ·

          2021-11-27 14:20

          大家好,我是熱心網(wǎng)友?—— 小林。

          有位讀者問(wèn)了,我這么一個(gè)問(wèn)題:

          不管是 RPC 或者 HTTP,只要傳輸?shù)膬?nèi)容是「對(duì)象」,要想在接收方還原出一摸一樣的「對(duì)象」,那就需要序列化和反序列化。

          那什么是序列化和反序列化呢?

          RPC 能幫助我們的應(yīng)用透明地完成遠(yuǎn)程調(diào)用,即調(diào)用其他服務(wù)器的函數(shù)就像調(diào)用本地方法一樣。發(fā)起調(diào)用請(qǐng)求的那一方叫做調(diào)用方,被調(diào)用的一方叫做服務(wù)提供方。

          調(diào)用方和服務(wù)提供方一般是不同的服務(wù)器,所以就需要通過(guò)網(wǎng)絡(luò)來(lái)傳輸數(shù)據(jù),并且 RPC 常用于業(yè)務(wù)系統(tǒng)之間的數(shù)據(jù)交互,需要保證其可靠性,所以 RPC 一般默認(rèn)采用 TCP 協(xié)議來(lái)傳輸。同時(shí), HTTP 協(xié)議也是建立在 TCP 之上的。

          網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)必須是二進(jìn)制數(shù)據(jù),但調(diào)用方請(qǐng)求的出入?yún)?shù)都是對(duì)象,而對(duì)象是肯定沒(méi)法直接在網(wǎng)絡(luò)中傳輸?shù)模枰崆鞍选笇?duì)象轉(zhuǎn)成二進(jìn)制數(shù)據(jù)」進(jìn)行網(wǎng)絡(luò)傳輸,這個(gè)轉(zhuǎn)換過(guò)程就做序列化。相反,服務(wù)提供方收到網(wǎng)絡(luò)數(shù)據(jù)后,需要將「二進(jìn)制數(shù)據(jù)轉(zhuǎn)成對(duì)象」,這個(gè)轉(zhuǎn)換過(guò)程就叫做反序列化。

          總結(jié)來(lái)說(shuō),序列化就是將對(duì)象轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)的過(guò)程,以方便傳輸或存儲(chǔ)。而反序列就是將二進(jìn)制轉(zhuǎn)換為對(duì)象的過(guò)程。

          為什么 RPC 經(jīng)常提到序列化呢?

          因?yàn)榫W(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)必須是二進(jìn)制數(shù)據(jù),所以在 RPC 調(diào)用中,對(duì)入?yún)?duì)象與返回值對(duì)象進(jìn)行序列化與反序列化是一個(gè)必須的過(guò)程。

          HTTP 什么時(shí)候需要序列化呢?

          舉個(gè)例子。

          當(dāng)客戶端和服務(wù)端交互的數(shù)據(jù)是 JSON,這時(shí)候發(fā)送方需要將 JSON 對(duì)象轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)發(fā)送到網(wǎng)絡(luò),接收方需要將接收到的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成 JSON 對(duì)象。

          說(shuō)了,這么多概念,接下來(lái)跟大家說(shuō)說(shuō)有哪些常用的序列化方式?

          JDK 原生序列化

          Java 語(yǔ)言中 JDK 就自帶有序列化的方式,舉個(gè) JDK 序列化的例子。

          注意,這個(gè)例子是將 Student 對(duì)象保存到文件,保存到文件的數(shù)據(jù)是二進(jìn)制數(shù)據(jù),所以并不是說(shuō)序列化只用在網(wǎng)絡(luò)傳輸場(chǎng)景里,只要是保存數(shù)據(jù)的場(chǎng)景只能是二進(jìn)制數(shù)據(jù)時(shí),就需要用序列化。

          import?java.io.*;

          public?class?Student?implements?Serializable?{
          ????//學(xué)號(hào)
          ????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");
          ????????
          ????????//初始化一個(gè)學(xué)生對(duì)象
          ????????Student?student?=?new?Student();
          ????????student.setNo(1);
          ????????student.setName("xiaolin");
          ????????
          ????????//將學(xué)生對(duì)象序列化到文件里
          ????????ObjectOutputStream?oos?=?new?ObjectOutputStream(fos);
          ????????oos.writeObject(student);
          ????????oos.flush();
          ????????oos.close();

          ????????//讀取文件中的二進(jìn)制數(shù)據(jù),并將數(shù)據(jù)反序列化為學(xué)生對(duì)象
          ????????FileInputStream?fis?=?new?FileInputStream(basePath?+?"student.dat");
          ????????ObjectInputStream?ois?=?new?ObjectInputStream(fis);
          ????????Student?deStudent?=?(Student)?ois.readObject();
          ????????ois.close();

          ????????System.out.println(deStudent);
          ????}
          }

          從上面的代碼,我們可以知道:

          • JDK 自帶的序列化具體的實(shí)現(xiàn)是由 ObjectOutputStream 完成的;

          • 而反序列化的具體實(shí)現(xiàn)是由 ObjectInputStream 完成的。

          既然序列化是將對(duì)象轉(zhuǎn)換為二進(jìn)制數(shù)據(jù),那序列化的過(guò)程的二進(jìn)制數(shù)據(jù)肯定是有某種固定的格式。

          比如 JDK 自帶的序列化的過(guò)程如下圖:

          序列化過(guò)程就是在讀取對(duì)象數(shù)據(jù)的時(shí)候,不斷加入一些特殊分隔符,這些特殊分隔符用于在反序列化過(guò)程中截?cái)嘤谩?/p>

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

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

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

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

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

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

          JSON

          JSON 數(shù)據(jù)的格式相信大家都很熟悉了吧,在 Web 應(yīng)用里特別常見(jiàn),通常后端和前端的耦合就是 JSON 數(shù)據(jù)。

          JSON 是典型的 Key-Value 方式,沒(méi)有數(shù)據(jù)類型。很多語(yǔ)言都實(shí)現(xiàn)了 JSON 序列化的第三庫(kù),所以 JSON 序列化的方式可以跨語(yǔ)言。

          比如,Java 語(yǔ)言中常用的 JSON 第三方類庫(kù)有:

          • Gson: 谷歌開(kāi)發(fā)的 JSON 庫(kù),功能十分全面。

          • FastJson: 阿里巴巴開(kāi)發(fā)的 JSON 庫(kù),性能十分優(yōu)秀。

          • Jackson: 社區(qū)十分活躍且更新速度很快。

          我這里說(shuō)個(gè) C++ 的 JSON 第三方庫(kù):JSON for Modern C++。

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

          因?yàn)?JSON 是 Key-Value 形式,所以 JSON for Modern C++ 的操作和標(biāo)準(zhǔn)容器 map 一樣,用關(guān)聯(lián)數(shù)組的“[]”來(lái)添加任意數(shù)據(jù)。

          這里貼幾個(gè)例子:

          //?給?JSON?for?Modern?C++?的?json?類取個(gè)別名
          using?json_t?=?nlohmann::json;??
          //?JSON對(duì)象
          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ù)后,就可以調(diào)用成員函數(shù) dump() 進(jìn)行初始化,得到 JSON 文本形式,也就是 JSON 字符串:

          cout?<endl;

          反序列化也很簡(jiǎn)單,只要調(diào)用靜態(tài)成員函數(shù) parse() 就行,直接得到 JSON 對(duì)象:

          //?JSON文本,原始字符串
          string?jsonStr?=?R"({???????????????
          ????"name":?"xiaolin",
          ????"age"?:?18
          })"
          ;

          //?從字符串反序列化
          json_t?j?=?json_t::parse(jsonStr);????

          //?驗(yàn)證序列化是否正確
          assert(j["age"]?==?18);????????
          assert(j["name"]?==?"xiaolin");

          對(duì)于通常的應(yīng)用來(lái)說(shuō),掌握了基本的序列化和反序列化就夠用了,如果想要了解 JSON for Modern C++ 其他特性,可以去看它的 Github。

          JSON 進(jìn)行序列化存在的問(wèn)題,因?yàn)?JSON 進(jìn)行序列化的額外空間開(kāi)銷比較大,對(duì)于大數(shù)據(jù)量服務(wù)這意味著需要巨大的內(nèi)存和磁盤(pán)開(kāi)銷,所以如果在傳輸數(shù)據(jù)量比較小的場(chǎng)景,就可以采用 JSON 序列化的方式。

          ProtoBuffer

          ProtoBuf 是由 Google 出品的,是一種輕便、高效的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式,可以用于結(jié)構(gòu)化數(shù)據(jù)序列化,支持 Java、Python、C++、Go 等語(yǔ)言。

          Protobuf 使用的時(shí)候必須寫(xiě)一個(gè) IDL(Interface description language)文件,在里面定義好數(shù)據(jù)結(jié)構(gòu),只有預(yù)先定義了的數(shù)據(jù)結(jié)構(gòu),才能被序列化和反序列化。

          下面是一個(gè)簡(jiǎn)單的 IDl 文件格式:

          syntax = "proto2";  // 使用第2版
          package sample; // 定義名字空間

          message Person { // 定義消息
          required string name = 1; // required表示必須字段
          required int32 id = 2;
          optional string email = 3; // optional字段可以沒(méi)有
          }

          寫(xiě)完 IDL 文件后,然后使用不同語(yǔ)言的 IDL 編譯器,生成序列化工具類。

          Protobuf 在 Github 有文檔介紹了不同語(yǔ)言是怎么使用編譯器生成序列化工具類了,我在這里就不介紹了。

          這里貼 C++ 和 Java 語(yǔ)言使用 Protobuf 相關(guān)接口進(jìn)行序列化和反序列化的例子。

          C++ 進(jìn)行序列化和反序列化的例子:

          //?類型別名
          using?person_t?=?sample::Person;????????

          //?聲明一個(gè)Protobuf對(duì)象
          person_t?p;?????????????????????????????

          //?設(shè)置每個(gè)字段的值?
          p.set_id(1);???????????????????????????
          p.set_name("xiaolin");
          p.set_email("[email protected]");

          //?序列化到字符串?
          string?enc;
          p.SerializeToString(&enc);

          //?反序列化
          person_t?p2;?
          p2.ParseFromString(enc);

          Java 進(jìn)行序列化和反序列化的例子:

          /**
          ?*
          ?*?//?IDl?文件格式
          ?*?synax?=?"proto3";
          ?*?option?java_package?=?"com.test";
          ?*?option?java_outer_classname?=?"StudentProtobuf";
          ?*
          ?*?message?StudentMsg?{
          ?*?//序號(hào)
          ?*?int32?no?=?1;
          ?*?//姓名
          ?*?string?name?=?2;
          ?*?}
          ?*?
          ?*/

          ?
          StudentProtobuf.StudentMsg.Builder?builder?=?StudentProtobuf.StudentMsg.newBuilder();
          builder.setNo(1);
          builder.setName("xiaolin");

          //把student對(duì)象轉(zhuǎn)化為byte數(shù)組
          StudentProtobuf.StudentMsg?msg?=?builder.build();
          byte[]?data?=?msg.toByteArray();

          //把剛才序列化出來(lái)的byte數(shù)組轉(zhuǎn)化為student對(duì)象
          StudentProtobuf.StudentMsg?deStudent?=?StudentProtobuf.StudentMsg.parseFrom(data);

          System.out.println(deStudent);

          現(xiàn)在很多大廠都在使用 Protobuf,而且 gRPC 就是基于 Protobuf 來(lái)做的序列化。

          參考資料:《C++ 實(shí)戰(zhàn)筆記》、《RPC 實(shí)戰(zhàn)與核心原理》


          總結(jié)

          將對(duì)象保存到文件,或者通過(guò)網(wǎng)絡(luò)傳輸給對(duì)方,都是需要將對(duì)象轉(zhuǎn)換二進(jìn)制數(shù)據(jù)才能完成,那么對(duì)象轉(zhuǎn)二進(jìn)制數(shù)據(jù)的過(guò)程就是序列化,相反的,二進(jìn)制數(shù)據(jù)轉(zhuǎn)對(duì)象的過(guò)程就是反序列化的過(guò)程。

          我也給你介紹了幾種常見(jiàn)的序列化方式:

          • JDK 自帶的序列化方式無(wú)法跨語(yǔ)言使用;

          • JSON 序列化大多數(shù)語(yǔ)言都支持,JSON 優(yōu)勢(shì)是使用起來(lái)簡(jiǎn)單,容易閱讀,應(yīng)用廣泛,缺點(diǎn)就是不適合大數(shù)據(jù)量的場(chǎng)景;

          • ProtoBuf 使用需要定義 IDL 文件,序列化后體積相比 JSON 小很多,現(xiàn)在很多大公司都在用,gRPC 框架使用 protobuf 序列化。


          推薦閱讀
          不鴿了,小林的「圖解網(wǎng)絡(luò) 3.0 」發(fā)布!
          小林的圖解系統(tǒng),大曝光!
          這一年半,過(guò)的不容易!
          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  狼友五月天成人在线视频 | 一级二级麻豆视频 | 台湾高清无码视频在线观看 | 国产一区二区三区四区久久 | a片黄色成人电影 |