二进制序列化
在計(jì)算機(jī)世界,萬(wàn)物皆01二進(jìn)制,包括各種各樣的文件格式和網(wǎng)絡(luò)協(xié)議,二進(jìn)制格式最為常見(jiàn)!NewLife.Core 內(nèi)置了完整的二進(jìn)制序列化框架 Binary,經(jīng)過(guò)十多年洗禮,發(fā)展到了第三代支持Handler處理器擴(kuò)展。Binary的同類框架有 Protobuf、Thrift、MessagePack。
Nuget包:NewLife.Core
源碼地址:https://github.com/NewLifeX/X/tree/master/NewLife.Core/Serialization/Binary
主要特性
Binary主要功能特性:
體積極小。Binary是Schemaless架構(gòu),不包含字段名和序號(hào),用最少的字節(jié)去保存數(shù)據(jù)
壓縮整數(shù)。大多數(shù)時(shí)候Int32字段保存的數(shù)字很小,采用七位壓縮編碼整數(shù)保存可以減少體積
格式簡(jiǎn)單。盡管Binary只有.NET版,但其格式非常簡(jiǎn)單,可以很容易在其它語(yǔ)言上實(shí)現(xiàn)
支持性很廣。Binary設(shè)計(jì)初衷,就是用于實(shí)現(xiàn)各種已知文件格式和通信協(xié)議,例如ZipFile
支持動(dòng)態(tài)特性。可根據(jù)某些字段值,生成不同消息類型,例如MQTT和DNS協(xié)議
可讀性較差。二進(jìn)制格式且沒(méi)有Schema,可讀性較差
無(wú)版本支持。需要讀寫(xiě)雙方約定好多版本格式的兼容
Binary設(shè)計(jì)理念,就是用最小的體積去保存數(shù)據(jù),且能夠靈活實(shí)現(xiàn)各種文件格式和通信協(xié)議的序列化。
直接序列化對(duì)象,在沒(méi)有使用額外壓縮算法的條件下,Binary幾乎是結(jié)果體積最小的序列化框架。
快速用法
想要序列化一個(gè)對(duì)象,或者反序列化一個(gè)數(shù)據(jù)流到對(duì)象,最直接的想法就是這樣
// 快速讀取 public static T FastRead<T>(Stream stream, Boolean encodeInt = true); // 快速寫(xiě)入 public static Packet FastWrite(Object value, Boolean encodeInt = true); public static void FastWrite(Object value, Stream stream, Boolean encodeInt = true);Binary.FastWrite 可以直接把一個(gè)對(duì)象序列化為數(shù)據(jù)包Packet,可以理解為字節(jié)數(shù)組Byte[]的包裝。
Binary.FastRead 從數(shù)據(jù)流中反序列化得到目標(biāo)類型的對(duì)象,這里必須指定目標(biāo)類型,否則Binary不知道應(yīng)該如何解析。
例子
[Fact] public void Fast() {var model = new MyModel { Code = 1234, Name = "Stone" };var pk = Binary.FastWrite(model);Assert.Equal(8, pk.Total);Assert.Equal("D2090553746F6E65", pk.ToHex());Assert.Equal("0gkFU3RvbmU=", pk.ToArray().ToBase64());var model2 = Binary.FastRead<MyModel>(pk.GetStream());Assert.Equal(model.Code, model2.Code);Assert.Equal(model.Name, model2.Name);var ms = new MemoryStream();Binary.FastWrite(model, ms);Assert.Equal("D2090553746F6E65", ms.ToArray().ToHex()); } private class MyModel {public Int32 Code { get; set; }public String Name { get; set; } }序列化帶有一個(gè)整型和一個(gè)字符串的對(duì)象,結(jié)果只有8個(gè)字節(jié)!
Packet用法可參考
此處為語(yǔ)雀文檔,點(diǎn)擊鏈接查看:https://www.yuque.com/go/doc/31527106
標(biāo)準(zhǔn)讀寫(xiě)
Binary主要成員
/// <summary>使用7位編碼整數(shù)。默認(rèn)false不使用</summary> public Boolean EncodeInt { get; set; } /// <summary>小端字節(jié)序。默認(rèn)false大端</summary> public Boolean IsLittleEndian { get; set; } /// <summary>使用指定大小的FieldSizeAttribute特性,默認(rèn)false</summary> public Boolean UseFieldSize { get; set; } /// <summary>使用對(duì)象引用,默認(rèn)true</summary> public Boolean UseRef { get; set; } = true; /// <summary>大小寬度。可選0/1/2/4,默認(rèn)0表示壓縮編碼整數(shù)</summary> public Int32 SizeWidth { get; set; } /// <summary>要忽略的成員</summary> public ICollection<String> IgnoreMembers { get; set; } /// <summary>處理器列表</summary> public IList<IBinaryHandler> Handlers { get; private set; } /// <summary>數(shù)據(jù)流。默認(rèn)實(shí)例化一個(gè)內(nèi)存數(shù)據(jù)流</summary> public virtual Stream Stream { get; set; } /// <summary>主對(duì)象</summary> public Stack<Object> Hosts { get; private set; } /// <summary>成員</summary> public MemberInfo Member { get; set; } /// <summary>字符串編碼,默認(rèn)utf-8</summary> public Encoding Encoding { get; set; } /// <summary>序列化屬性而不是字段。默認(rèn)true</summary> public Boolean UseProperty { get; set; } // 處理器 public Binary AddHandler(IBinaryHandler handler); public Binary AddHandler<THandler>(Int32 priority = 0); public T GetHandler<T>(); // 寫(xiě)入 public virtual Boolean Write(Object value, Type type = null); // 讀取 public virtual Object Read(Type type); public T Read<T>(); public virtual Boolean TryRead(Type type, ref Object value);Stream 最為重要,代表序列化和反序列化的數(shù)據(jù)流,默認(rèn)實(shí)例化一個(gè)內(nèi)存流。
EncodeInt 指定使用壓縮編碼整數(shù),效果非常明顯!
IsLittleEndian 部分協(xié)議使用大端字節(jié)序。
UseFieldSize 部分協(xié)議的長(zhǎng)度位和數(shù)據(jù)區(qū)并沒(méi)有挨在一起,需要借助FieldSizeAttribute特性。例如ZipEntry中有這么一段:
/// <summary>文件名長(zhǎng)度</summary> private readonly UInt16 FileNameLength; /// <summary>擴(kuò)展數(shù)據(jù)長(zhǎng)度</summary> private readonly UInt16 ExtraFieldLength; // ZipDirEntry成員 /// <summary>注釋長(zhǎng)度</summary> private readonly UInt16 CommentLength; // ZipDirEntry成員 /// <summary>分卷號(hào)。</summary> public UInt16 DiskNumber; // ZipDirEntry成員 /// <summary>內(nèi)部文件屬性</summary> public UInt16 InternalFileAttrs; // ZipDirEntry成員 /// <summary>擴(kuò)展文件屬性</summary> public UInt32 ExternalFileAttrs; // ZipDirEntry成員 /// <summary>文件頭相對(duì)位移</summary> public UInt32 RelativeOffsetOfLocalHeader; /// <summary>文件名,如果是目錄,則以/結(jié)束</summary> [FieldSize("FileNameLength")] public String FileName; /// <summary>擴(kuò)展字段</summary> [FieldSize("ExtraFieldLength")] public Byte[] ExtraField; // ZipDirEntry成員 /// <summary>注釋</summary> [FieldSize("CommentLength")] public String Comment;IgnoreMembers 指定某些成員不參與序列化,支持動(dòng)態(tài)指定。例如ZipFile的目錄實(shí)體和文件實(shí)體,需要序列化的字段有所不同。
Encoding 指定序列化字符串時(shí)使用的文本編碼。
設(shè)置好各種參數(shù)后,就可以Write/Read來(lái)序列化或反序列化對(duì)象了。安全起見(jiàn),建議每個(gè)Binary只用一次,重復(fù)使用可能有意想不到的后果。
自定義擴(kuò)展
Binary設(shè)計(jì)時(shí)使用Handler處理器架構(gòu),Write/Read內(nèi)部實(shí)際上是逐個(gè)遍歷Handler,直到找到能夠處理的Handler為止。因此Handler也有優(yōu)先級(jí),其中基礎(chǔ)數(shù)據(jù)類型BinaryGeneral處理器優(yōu)先級(jí)最高。
BinaryGeneral 負(fù)責(zé)處理數(shù)字、布爾、時(shí)間日期、字符串等等基礎(chǔ)數(shù)據(jù)類型。
BinaryNormal 負(fù)責(zé)處理字節(jié)數(shù)組、Guid、Packet等常見(jiàn)類型。
BinaryList 負(fù)責(zé)處理數(shù)組和列表。
BinaryDictionary 負(fù)責(zé)處理字典。
BinaryComposite 負(fù)責(zé)處理復(fù)雜對(duì)象,反射各成員,遞歸序列化。該處理器優(yōu)先級(jí)最低。
來(lái)看看怎么樣自定義一個(gè)處理器,以顏色處理器為例:
/// <summary>顏色處理器。</summary> public class BinaryColor : BinaryHandlerBase {/// <summary>實(shí)例化</summary>public BinaryColor(){Priority = 0x50;}/// <summary>寫(xiě)入對(duì)象</summary>/// <param name="value">目標(biāo)對(duì)象</param>/// <param name="type">類型</param>/// <returns></returns>public override Boolean Write(Object value, Type type){if (type != typeof(Color)) return false;var color = (Color)value;WriteLog("WriteColor {0}", color);Host.Write(color.A);Host.Write(color.R);Host.Write(color.G);Host.Write(color.B);return true;}/// <summary>嘗試讀取指定類型對(duì)象</summary>/// <param name="type"></param>/// <param name="value"></param>/// <returns></returns>public override Boolean TryRead(Type type, ref Object value){if (type != typeof(Color)) return false;var a = Host.ReadByte();var r = Host.ReadByte();var g = Host.ReadByte();var b = Host.ReadByte();var color = Color.FromArgb(a, r, g, b);WriteLog("ReadColor {0}", color);value = color;return true;} }最后只需要掛載到Binary上即可序列化和反序列化帶有Color類型的成員
var bn = new Binary(); bn.AddHandler<BinaryColor>();總結(jié)
.NET內(nèi)部自帶二進(jìn)制序列化BinaryFormatter,它會(huì)帶上大量額外信息,導(dǎo)致體積很大,基本上很少用到。
Binary設(shè)計(jì)的初衷是序列化各種文件格式和通信協(xié)議,因此并沒(méi)有過(guò)多考慮作為RPC通信格式。實(shí)際上NewLife組件自己的RPC框架ApiServer并沒(méi)有使用Binary,而是選擇了兼容性比較好的Json。
在中通的100億Redis大數(shù)據(jù)中,盡管是二進(jìn)制kv數(shù)據(jù),同樣沒(méi)有用到Binary。因?yàn)樗枰獙?duì)字節(jié)數(shù)據(jù)進(jìn)行極致控制,并且需要做多版本兼容。因此它實(shí)際上是直接讀寫(xiě)二進(jìn)制數(shù)據(jù)流,然后借用了Binary的一些輔助方法。
總結(jié)
- 上一篇: 谷歌微软高通反对英伟达收购ARM 值得国
- 下一篇: 让 gRPC 提供 REST 服务