1. 程式人生 > >開源!一款功能強大的高效能二進位制序列化器Bssom.Net

開源!一款功能強大的高效能二進位制序列化器Bssom.Net

### 好久沒更新部落格了,我開源了一款高效能的二進位制序列化器Bssom.Net和新穎的二進位制協議Bssom,歡迎大家Star,歡迎參與專案貢獻! **Net開源技術交流群 976304396**,禁止水,只能討論技術, 歡迎與我討論和效能相關的技術話題! 另外,我還在抖音申請了一個賬號,用來記錄自己的日常生活, 想了解我平常是怎麼寫程式碼的嗎? 來關注我一下,哈哈! **抖音號: 198152455** Bssom.Net專案地址: [https://github.com/1996v/Bssom.Net](https://github.com/1996v/Bssom.Net) Bssom協議地址: [https://github.com/1996v/Bssom](https://github.com/1996v/Bssom) ## A small, high performance, powerful serializer using bssom binary protocol [![Nuget](https://img.shields.io/nuget/v/BssomSerializer.svg)](https://www.nuget.org/packages/BssomSerializer/) **Bssom.Net**是一個使用**bssom結構協議**實現的高效能結構化二進位制**序列化器**,它具有以下特點,**小巧,快速,功能性強**. 1. 小巧,檔案僅**300多k** 2. 快速,它具有**一流**的序列化和反序列化[效能](#1.效能) 3. 功能性強: * 可以獲取物件被序列化後的大小而**不用完整**序列化物件 * 可以讀取物件中的某個元素而**不用完整**的反序列化物件 * 可以更改物件中的某個元素而**不用完整**的序列化 * 序列化後的格式具有**自描述性** ## 為什麼需要? 目前c#已經有很多二進位制序列化器, 但這些序列化器都只提供了單一的序列化和反序列化功能. Bssom.Net採取了[Bssom協議](https://github.com/1996v/Bssom), 使序列化後的資料具有**結構化特性**, 且擁有直接對欄位進行編組的功能, 這使得Bssom.Net能做到其它序列化器所達不到的事情. * 當我想在序列化物件時知道物件被序列化後的大小, 以提前來選擇該物件應該被序列化的正確位置(如資料庫引擎的FSM演算法), 那麼Bssom.Net能夠滿足你 * 當我擁有一個大的二進位制資料, 但是我只想無合約的讀取其中一個欄位, 以避免完整的反序列化開銷, 那麼Bssom.Net能夠滿足你 * 當我擁有一個已經被序列化後的資料包, 我只想無合約的修改其中一個欄位, 以避免重新序列化的開銷, 那麼Bssom.Net能夠滿足你 * 當我想讓物件被序列化後仍能保留型別資訊, 而不用依賴實體, 那麼Bssom.Net能夠滿足你 ### 什麼是Bssom協議? > [Bssom](https://github.com/1996v/Bssom)(Binary search algorithm structure model object binary marshalling)是一個使用二分查詢演算法模型對物件進行結構化編組的協議,被編組後的資料具有特殊的元資料資訊,根據這些元資料資訊可以高效的僅讀取和更改物件中的某個元素,這樣可以在對大物件進行序列化和反序列化的過程中不必因為只讀取或只寫入一個欄位而造成完整的序列化開銷。 ## 大綱 ## * [1.效能](#1效能) * [2.讀寫器](#2讀寫器) * [IBssomBuffer](#ibssombuffer) * [IBssomBufferWriter](#ibssombufferwriter) * [3.格式化器](#3格式化器) * [4.解析器](#4解析器) * [PrimitiveResolver](#primitiveresolver) * [AttributeFormatterResolver](#attributeformatterresolver) * [BuildInResolver](#buildinresolver) * [BssomValueResolver](#bssomvalueresolver) * [IDictionaryResolver](#idictionaryresolver) * [ICollectionResolver](#icollectionresolver) * [MapCodeGenResolver](#mapcodegenresolver) * [ObjectResolver](#objectresolver) * [CompositedResolver](#compositedresolver) * [5.擴充套件](#5擴充套件) * [6.高階API](#6高階API) * [BssomSerializer](#bssomserializer) * [BssomSerializerOptions](#bssomserializeroptions) * [BssomSerializeContext](#bssomserializecontext) * [7.欄位編組](#7欄位編組) * [BssomFieldMarshaller](#BssomFieldMarshaller) * [簡單欄位訪問語言](#簡單欄位訪問語言) * [自定義欄位訪問形式介面](#自定義欄位訪問形式介面) * [8.動態程式碼生成](#8動態程式碼生成) * [9.特性](#9特性) * [10.更多的可能性](#10更多的可能性) * [11.如何使用](#11如何使用) * [Size](#size) * [Serialize](#serialize) * [Deserialize](#deserialize) * [ReadValue](#readvalue) * [ReadAllMapKeys](#readallmapkeys) * [TryWriteValue](#trywritevalue) * [如何使用特性](#如何使用特性) * [如何定義擴充套件](#如何定義擴充套件) * [12.如何參與專案貢獻](#12如何參與專案貢獻) * [13.誰在使用](#13誰在使用) * [14.其它](#14其它) ## 1.效能 ![](https://user-images.githubusercontent.com/30827194/97228887-808af380-1812-11eb-846d-821ed0a7d978.png) 這裡是與.NET平臺下非常優秀的兩款序列化程式([MessagePack](https://github.com/neuecc/MessagePack-CSharp) 和 [Protobuf-net](https://github.com/protobuf-net/protobuf-net))進行效能比較的基準. 柱狀資料代表執行相同任務所花費的時間, **越低代表性能越快**, 折線資料代表執行相同任務所產生的GC, **越低代表在執行中所產生的垃圾越少** , 從效能比較結果可以看出Bssom.Net的效能是非常優異的. Bssom.Net使用很多技術來提高效能. * 使用**記憶體池**技術, 用於寫入的記憶體可以**複用** * 使用表示式和Emit**動態程式設計**技術, 對型別進行了特殊處理, 且避免值型別裝箱拆箱 * 使用泛型**靜態快取**, 避免了字典查詢開銷 * 包裝了異常丟擲程式碼, 以增加**內聯**的可能性 * 更多的對**強型別**進行呼叫, 而不是介面抽象 * **預處理**Map2型別的元資料, 在序列化時不需要對其進行再次編碼 * 在查詢Map2鍵時, 提前**固定區域性引用**, 而不是標準函式呼叫 * 解析Map1型別時, 自動構建8位元組的**自動機**跳躍查詢 * 值得一提的是, 出於減少依賴, 減少體積的目的, Bssom.Net並沒有依賴`System.Memory.dll`, 因此無法使用`Span`,`Memory`等型別, 這意味著Bssom.Net的實現將無法使用`ByReference`這一JIT內部特性, 因此目前的讀寫器將不具備讀寫區域性化和去虛擬化及內聯呼叫的這三個效能優化點 ( 但即使這樣, 目前的Bssom.Net效能依然非常優秀 ) , 若將來有可能支援`Span`型別的話, 那麼Bssom.Net將會通過一些額外的效能技巧來再次提升效能. ## 2.讀寫器 Bssom.Net對於讀取和寫入的入口並不是直接使用原生的`Byte[]`, 而是提供了緩衝區介面`IBssomBuffer`和寫入器介面`IBssomBufferWriter`. 與原生的`byte[]`不同, 介面將更加靈活, 實現`IBssomBuffer`後可以從任意來源來讀取資料, 實現`IBssomBufferWriter`後可以將資料寫在任意地方(比如非連續的片段) ### IBssomBuffer `IBssomBuffer`是一個用於序列化的緩衝區介面, 提供了讀取的行為. 方法 | 描述 ------------|----------- Position | 緩衝區中的當前位置 ReadRef | 從當前緩衝區中的位置讀取指定大小序列的引用 Seek | 設定當前緩衝區的位置 SeekWithOutVerify | 設定當前緩衝區的位置, 並且不對position的邊界進行驗證 TryReadFixedRef | 嘗試從當前緩衝區中的位置讀取一個可以固定的位元組序列的引用, 當進行Seek操作的時候不會影響被固定位元組的引用位置 UnFixed | 用於取消由TryReadFixedRef所固定的引用, 此方法的呼叫始終和TryReadFixedRef對稱 ### IBssomBufferWriter `IBssomBufferWriter`是基於緩衝區的寫入介面, 提供了寫入行為 方法 | 描述 -----|----- Buffered|在緩衝區中已寫入位元組的數目 Advance| 用於指示已寫入緩衝區的部分 Position| 寫入器的當前位置 Seek | 設定當前寫入器的位置 SeekWithOutVerify | 設定當前寫入器的位置, 並且不對Buffered的邊界進行驗證 GetRef | 從當前位置獲取用於寫入的位元組序列的引用 CanGetSizeRefForProvidePerformanceInTryWrite | 在欄位編組中, 當前位置是否能提供指定大小的位元組序列引用以用來提供內部某些型別寫入的效能 GetBssomBuffer | 獲取當前寫入器所使用的緩衝區 **Bssom.Net內部已經對`byte[]`, `Stream`進行了`IBssomBuffer`和`IBssomBufferWriter`介面的封裝, 使用者無需手動封裝** ## 3.格式化器 **格式化**是Bssom.Net將.Net物件和Bssom格式進行互相轉換的一個過程. Bssom.Net通過`IBssomFormatter`來實現對物件的格式化. API | 描述 -----|---------- Size | 獲取物件被序列化後的大小 Serialize | 將物件序列化成Bssom二進位制格式 Deserialize | 將Bssom二進位制格式反序列化成物件 Bssom.Net內部已經內建了許多格式化器, 如.NET的基元型別, 鍵值對型別, 可迭代型別... 他們在`Bssom.Serializer.Formatters`名稱空間下, 你可以找到它並直接呼叫它. 如果你不需要特殊的處理某個型別的話, 那麼這些格式化器基本可以覆蓋你的大部分需求. 而如何找到格式化器, 這則是解析器所需要做的. ## 4.解析器 **解析**是將.Net型別物件**獲取**到對應的**格式化器**的一個過程.Bssom.Net通過`IFormatterResolver`來實現對物件的解析. API | 描述 -----|------ GetFormatter | 獲取物件的格式化器例項 解析器通常具備解析型別和儲存格式化器這兩種功能, Bssom.Net中已實現的解析器在內部會對.net型別進行格式化器的查詢, 然後通過靜態泛型的特性快取被找到的格式化器, 完成了將**一個或一組**.net型別**繫結**到對應的格式化器的這樣過程. `IFormatterResolver`是Bssom.NET開始對物件序列化的最上層的入口, 他們在`Bssom.Serializer.Resolvers`名稱空間下. 名稱 | 描述 ------------|------------- PrimitiveResolver | 該解析器提供了`sbyte`,`Int16`,`Int32`,`Int64`,`byte`,`UInt16`,`UInt32`,`UInt64`,`Single`,`Double`,`bool`,`char`,`Guid`,`Decimal`,`string`,`DateTime`的型別的解析器 AttributeFormatterResolver | 獲取並提供使用者自定義格式化器的例項 BuildInResolver | 提供了`StringBuilder`,`BitArray`,`DataTable`等型別的解析器 BssomValueResolver | 提供了`BssomValue`型別的解析器 IDictionaryResolver | 獲取和生成具有`IDictionary`行為的型別的解析器, 該解析器抽象了BCL中對於鍵值對定義的行為規則, 為滿足該規則的物件進行動態解析程式碼的生成.在解析器內部, 將通過執行時的配置選項來選擇`Map1`或`Map2`的兩種格式 ICollectionResolver | 獲取和生成具有`IColloction`行為的型別的解析器, 該解析器抽象了BCL中對於收集器定義的行為規則, 為滿足該規則的物件進行動態解析程式碼的生成. 在解析器內部, 如果集合中的元素型別為基元型別, 則將其解析成`Array1`格式, 否則解析為`Array2`格式 MapCodeGenResolver | 獲取和生成物件的公開欄位和屬性進行BssomMap型別編碼的解析器, 若物件為介面, 則會自動生成該介面的實現作為反序列化的載體.在解析器內部, 始終將型別解析為`Map2`格式, 且提供`Map1`和`Map2`兩種格式的反序列化程式碼 ObjectResolver | 提供了`Object`型別的解析器 CompositedResolver | 複合解析器,組合了`Object`,`Primitive`,`Attribute`,`BssomValue`,`BuildIn`,`IDictionary`,`ICollection`,`MapCodeGen`解析器 > 因為`IDictionaryResolver`和`ICollectionResolver`中定義的足夠抽象的規則,Bssom.Net不需要為未來.NET可能出現的新的`IDictionary`或`IColloction`實現而編寫特定的解析程式碼. 在Bssom.Net中可以通過`BssomSerializerOptions`中的`FormatterResolver`屬性來注入序列化所需要的解析器, 預設為`CompositedResolver`, `CompositedResolver`將會對型別依次從 `Object`,`Primitive`,`Attribute`,`BssomValue`,`BuildIn`,`IDictionary`,`ICollection`,`MapCodeGen`解析器中進行查詢, 直到找到對應的解析器. ## 5.擴充套件 讓我們看一下Bssom.Net序列化的過程: input T -> Call serialize(T) -> Find BssomResolver -> Provide type formatter -> formatter.Serialize(T); 在整個序列化的過程中, 每個步驟都是**透明**的, 這意味著若使用者對Bssom.Net內部定義的解析器或格式化器不滿意的話, 則可以自己擴充套件它. 使用者可以自己通過實現`IFormatterResolver`和`IBssomFormatter`來**替代預設的解析器**, 在`Bssom.Serializer.Binary.BssomBinaryPrimitives`(在即將到來的小版本中將重構該類)和讀寫器本身所暴露的公開API中提供對Bssom格式的低階寫入和讀取實現. 簡單示例可以參考[更多可能介紹](#10更多的可能性) ## 6.高階API ### BssomSerializer `BssomSerializer`是Bssom最上層的API, 在`Bssom.Serializer`名稱空間下, 是Bssom開始工作的入口. 它的**靜態方法**構成了Bssom.Net的**主要API**. API | 描述 | 過載 --------|----------|------------- Size | 在不進行序列化的情況下, 獲取物件被序列化後的二進位制資料大小 |(t, option),(ref context, t) Serialize | 將給定的值序列化為Bssom二進位制 | (byte[], t, option), (stream, t, option), (IBssomBufWriter, t, option), (ref context, t) Deserialize | 將Bssom二進位制資料反序列化成.net物件 | (byte[], option),(stream, option),(IBssomBuf, option),(ref context) SerializeAsync | 非同步的序列化給定的值為Bssom二進位制 | 同上 DeserializeAsync | 非同步的將Bssom二進位制資料反序列化成.net物件 | 同上 ### BssomSerializerOptions `BssomSerializer`作為最上層的API,我們在呼叫它時,需要傳遞一個可空的`BssomSerializerOptions`型別的Option引數. `BssomSerializerOptions`是Bssom在整個序列化工作期間所需要使用的配置. 預設為`BssomSerializerOptions.Default`. - **FormatterResolver** : 在Option中,你可以為`FormatterResolver`**註冊解析器**, 如果沒有手動註冊, 則使用預設的`CompositedResolver`, Bssom將總是通過`FormatterResolver`來對型別進行解析. - **Security** : 這是用於序列化期間的**安全**相關選項, 目前僅提供了在反序列化期間對深度的驗證,預設為 **不限制** - **IsPriorityToDeserializeObjectAsBssomValue** : 該選項決定了反序列化時是否將Object型別轉換為BssomValue型別, 如果為`false`, 則預設反序列化為原生型別. 預設為`false`. - **IsUseStandardDateTime** : Bssom.Net對`DateTime`型別實現了標準的**Bssom協議Unix格式** 和 **.NET平臺的本地格式**, 本地格式具有更少的位元組, 但不具備和其它平臺的互動性, 預設為`false`. - **IDictionaryIsSerializeMap1Type** : 此選項決定了對具有`IDictionary`行為的型別預設使用哪種格式進行序列化, 如果為`true`則使用`Map1`格式, 否則為`Map2`格式. 預設為`true` ### BssomSerializeContext `BssomSerializeContext`提供了序列化期間所使用的上下文資訊, 這其中也包括了`BssomSerializerOptions` - **BssomSerializerOptions** : 序列化期間所使用的**配置資訊** - **ContextDataSlots** : 提供了一個數據槽, 供使用者在序列化期間自己儲存和讀取的一個**儲存介質** - **CancellationToken** : 一個序列化操作取消的標記, 使用者可以**中途取消**正在進行的序列化操作 ## 7.欄位編組 Bssom.Net擁有讀取欄位而**不用完全反序列化**和更改值而不用完全序列化功能, 這是因為[Bssom協議](https://github.com/1996v/Bssom)有著良好的結構化特徵, 在Bssom.Net的實現裡, 這樣的功能則暴露在`BssomFieldMarshaller`中. ### BssomFieldMarshaller `BssomFieldMarshaller`提供一套API用於對被序列化後的資料進行**更低粒度**的控制. API | 描述 -----|-------- IndexOf | 通過特殊的輸入格式來獲取被指定的物件在Bssom二進位制中的位置,返回偏移量資訊 ReadValue | 通過指定的偏移量資訊來讀取整個元素 ReadValueType | 通過指定的偏移量資訊僅讀取元素型別 ReadValueTypeCode | 通過指定的偏移量資訊僅讀取元素型別的二進位制碼 ReadValueSize | 通過指定的偏移量資訊來獲取元素在Bssom二進位制中所儲存的大小 ReadArrayCountByMapType | 通過指定的偏移量資訊來讀取BssomArray的元素數量 ReadAllKeysByMapType | 通過指定的偏移量資訊來讀取BssomMap中的元資料(包含Key和值的偏移量) TryWrite | 通過指定的偏移量資訊在Bssom二進位制中重新對值進行寫入, 若寫入值的寬度大於被寫入槽的寬度,則失敗 每種方法都提供了 `byte[]` 和 `IBssomBuf` 的過載 ### 簡單欄位訪問語言 Bssom.Net為`IndexOf`定義了一種簡單的欄位訪問語言, 該語言共定義了兩種訪問形式, 一種是訪問`Map`型別(該Map型別的鍵必須為`String`型別), 一種是訪問`Array`型別. 兩種訪問形式可以自由組合. - [Key] : 代表通過`Key`來訪問`Map`型別的值, 輸入的`Key`只表示`String`型別 - $Index : 代表通過下標來訪問`Array`型別的元素, 輸入的Index只能是整數型別 假設有如下資料 ```c# { "Postcodes" : { "WuHan" : [430070,430071,430072,430073], "XiangYang" : [441000,441001,441002] }, "Province" : "HuBei" } ``` 可以通過如下方式進行元素訪問, 在[示例](#readvalue)中可以瞭解更多細節 ```c# [Postcodes][WuHan]$1 => 4330071 [Province] => "HuBei" ``` ### 自定義欄位訪問形式介面 Bssom.Net為`IndexOf`提供了`IIndexOfInputSource`介面用來接收自定義的欄位訪問源, 使用該介面後Map型別的Key將不再受限制, Key可以為任意輸入型別. `IndexOfObjectsInputSource` 是 Bssom.Net為使用者提供的`IIndexOfInputSource`介面的**通用實現**. 它接收一組可迭代的物件,當呼叫IndexOf的時候, 將依次對物件進行迭代. 假設有如下資料 ```c# { 2018-01-01 : { 0 : ["Rain1","Rain2","Rain3"], 4 : ["Rain4","Fair5","Fair6"] } } ``` 可以通過如下方式進行元素訪問, 在[示例](#readvalue)中可以瞭解更多細節 ```c# new IndexOfObjectsInputSource(new Entry[]{ new Entry(DateTime.Parse("2018-01-01"),ValueIsMapKey: true), new Entry(3,ValueIsMapKey: true), new Entry(1,ValueIsMapKey: false), }) output => "Fair5" ``` ## 8.動態程式碼生成 Bssom.Net對`IDictionaryResolver`, `ICollectionResolver`, `MapCodeGenResolver`, `ObjectResolver` 使用了動態程式碼生成技術, 通過**表示式樹和Emit**共同生成執行時程式碼, 如果應用程式是純AOT環境, 則將不支援. 在`MapCodeGenResolver`中對`Map1`型別的反序列化使用了以8位元組(64位字長)為單位的類字首樹的自動機查詢模式, 這是非常有效且快速的方式, 它避免了對字串進行完全Hash運算以及字元比較開銷, 通過對`MapCodeGenResolver.Save()`方法你將看到這些自動生成的程式碼. ![](https://user-images.githubusercontent.com/30827194/97230916-b2518980-1815-11eb-891d-12fee0f2fe0a.png) `MapCodeGenResolver`中對`Map2`型別的反序列化則使用了內建的[Bssom協議](https://github.com/1996v/Bssom)的Map格式查詢程式碼,該程式碼是狀態機模式編寫, 分為快速和低速版, 這取決於[讀取器](#ibssombuffer)是否能夠提供 [TryReadFixedRef](#tryreadfixedref). ![](https://user-images.githubusercontent.com/30827194/97229613-99e06f80-1813-11eb-98ca-db941ce3d6d3.png) 另外,對於`Size`方法,MapCodeGenResolver的處理也是非常快速的,因為它已經提前計算好了元資料的大小,並且內聯了基元欄位本身的固定大小. ![](https://user-images.githubusercontent.com/30827194/97229619-9e0c8d00-1813-11eb-8954-df92e96c7d18.png) ## 9.特性 Bssom.Net中目前擁有5個特性. - **AliasAttribute** : 別名特性, 用於修改Map格式物件欄位在二進位制中所儲存的欄位名稱 - **BssomFormatterAttribute** : 自定義格式化特性, 當欄位屬性或型別被該特性標記後, 此型別的格式化將採用該特性所指定的格式化器 - **IgnoreKeyAttribute** : 忽略某一個Key, 序列化時將忽略被標記的欄位, 適用於Map格式 - **OnlyIncludeAttribute** : 僅包含某一個Key, 序列化時僅包含該Key, 適用於Map格式, 與`IgnoreKeyAttribute`作用相反,優先順序更高 - **SerializationConstructorAttribute** : 為型別的反序列化指定一個建構函式 ## 10.更多的可能性 你可以自己編寫[解析器](#4解析器), 編寫[格式化器](#3格式化器), 也可以定義你自己的特性, 也可以封裝用於序列化的[Option](#bssomserializeroptions), 並且Bssom.Net還提供了上下文[資料槽](#contextdataslots)的支援, 這可以讓序列化行為變得多樣性. 如果你能為Bssom.Net提供有用或者側重於高效能的**擴充套件包**, 那麼請您告訴我. 下面示例編寫了以String型別為原型的解析器, 該解析器通過與上下文互動的方式來帶來字串型別序列化效能的提升. ```c# public sealed class MyStringFormatterResolver : IFormatterResolver { public static MyStringFormatterResolver Instance = new MyStringFormatterResolver(); public IBssomFormatter GetFormatter() { return FormatterCache.Formatter; } private static class FormatterCache { public static readonly IBssomFormatter Formatter; static FormatterCache() { if (typeof(T) == typeof(string)) Formatter = (IBssomFormatter)(object)MyStringFormatter.Instance; } } } ``` ```c# public sealed class MyStringFormatter : IBssomFormatter { public static MyStringFormatter Instance = new MyStringFormatter(); public string Deserialize(ref BssomReader reader, ref BssomDeserializeContext context) { if (reader.TryReadNull()) { return null; } reader.EnsureType(BssomType.StringCode); int dataLen = reader.ReadVariableNumber(); ref byte refb = ref reader.BssomBuffer.ReadRef((int)dataLen); fixed (byte* pRefb = &refb) { return new string((sbyte*)pRefb, 0, (int)dataLen, UTF8Encoding.UTF8); } } public void Serialize(ref BssomWriter writer, ref BssomSerializeContext context, string value) { if (value == null) { writer.WriteNull(); return; } int valueUtf8Size = context.ContextDataSlots.PopMyStringSize(); writer.WriteBuildInType(BssomType.StringCode); writer.WriteVariableNumber(valueUtf8Size); ref byte refb = ref writer.BufferWriter.GetRef(valueUtf8Size); fixed (char* pValue = value) fixed (byte* pRefb = &refb) { UTF8Encoding.UTF8.GetBytes(pValue, value.Length, pRefb, valueUtf8Size); } writer.BufferWriter.Advance(valueUtf8Size); } public int Size(ref BssomSizeContext context, string value) { if (value == null) return BssomBinaryPrimitives.NullSize; int dataSize = UTF8Encoding.UTF8.GetByteCount(value); context.ContextDataSlots.PushMyStringSize(dataSize); return BssomBinaryPrimitives.BuildInTypeCodeSize + dataSize; } } ``` ```c# public void MyTest() { var option = BssomSerializerOptions.Default.WithFormatterResolver(MyStringFormatterResolver.Instance); string str = RandomHelper.RandomValue(); BssomSizeContext sizeContext = new BssomSizeContext(option); int len = BssomSerializer.Size(ref sizeContext, str); if (len > 1000) throw new Exception("Size of value storage binary exceeded"); BssomSerializeContext serContext = new BssomSerializeContext(option); sizeContext.ContextDataSlots.SetMyStringStack(serContext.ContextDataSlots); var bytes = BssomSerializer.Serialize(ref serContext, str); var deStr = BssomSerializer.Deserialize(bytes); Assert.Equal(str,deStr); } ``` 上面的程式碼是單獨為String定義了一個新的解析器和新的格式化器, 該格式化器可以將Size方法中對字串計算的UTF8大小儲存在上下文中, 這樣在序列化時不用重複對String再做一次UTF8大小計算. ## 11.如何使用 Bssom.Net是無合約的, 開箱即用, 這裡有些示例程式碼. ### Size [BssomSerializer.Size](#sizeapi) 方法用於 獲取物件被序列化後的二進位制資料大小,高效能的內部實現,幾乎無開銷 ```c# //獲取值被序列化後的大小 object value = RandomHelper.RandomValue(); int size = BssomSerializer.Size(value, option: BssomSerializerOptions.Default); ``` ```c# //使用上下文獲取值被序列化後的大小 BssomSizeContext context = new BssomSizeContext(BssomSerializerOptions.Default); object value = RandomHelper.RandomValue(); int size = BssomSerializer.Size(ref context, value); ``` ### Serialize [BssomSerializer.Serialize](#serializeapi) 方法用於 將給定的值序列化為Bssom二進位制,高效能的內部實現,以下是部分常用方法,每個方法都擁有CancellationToken的過載 ```c# //直接對物件進行序列化,將返回一個被序列化後的位元組陣列 object value = RandomHelper.RandomValue(); byte[] binary = BssomSerializer.Serialize(value, option: BssomSerializerOptions.Default); ``` ```c# //將物件序列化到指定的位元組陣列中,若容量不夠將自動擴容,最終返回序列化的位元組數 object value = RandomHelper.RandomValue(); byte[] buf = local(); int serializeSize = BssomSerializer.Serialize(ref buf, 0, value, option: BssomSerializerOptions.Default); ``` ```c# //將物件序列化到自定義的寫入器中 object value = RandomHelper.RandomValue(); IBssomBufferWriter writer = new Impl(); BssomSerializer.Serialize(value, writer, option: BssomSerializerOptions.Default); ``` ```c# //使用序列化上下文進行序列化 object value = RandomHelper.RandomValue(); BssomSerializeContext context = new BssomSerializeContext(BssomSerializerOptions.Default); byte[] binary = BssomSerializer.Serialize(ref context, value); ``` ```c# //將物件序列化到流中 object value = RandomHelper.RandomValue(); Stream stream = new MemoryStream(); BssomSerializer.Serialize(stream, value, option: BssomSerializerOptions.Default); ``` ```c# //非同步的將物件序列化到流中 object value = RandomHelper.RandomValue(); Stream stream = new MemoryStream(); await BssomSerializer.SerializeAsync(stream, value, option: BssomSerializerOptions.Default); ``` ### Deserialize [BssomSerializer.Deserialize](#deserializeapi) 方法用於 將給定的Bssom緩衝區反序列化為物件,高效能的內部實現,以下是部分常用方法,每個方法都擁有CancellationToken的過載 ```c# //從給定的位元組陣列中反序列化物件 byte[] buf = remote(); T value = BssomSerializer.Deserialize(buf, 0, out int readSize, option: BssomSerializerOptions.Default); ``` ```c# //從給定的buffer中反序列化物件 IBssomBuffer buffer = remote(); object value = BssomSerializer.Deserialize(buffer, option: BssomSerializerOptions.Default); ``` ```c# //使用上下文從給定的buffer中反序列化物件 BssomDeserializeContext context = new BssomDeserializeContext(BssomSerializerOptions.Default); IBssomBuffer buffer = remote(); object value = BssomSerializer.Deserialize(ref context, buffer); ``` ```c# //從流中反序列化物件 Stream stream = remote(); object value = BssomSerializer.Deserialize(stream, option: BssomSerializerOptions.Default); ``` ```c# //非同步的從流中反序列化物件 Stream stream = remote(); object value = await BssomSerializer.DeserializeAsync(stream, option: BssomSerializerOptions.Default); ``` ```c# //傳遞一個Type, 從流中反序列化物件為指定的Type型別 Stream stream = remote(); Type type = typeof(class); object value = BssomSerializer.Deserialize(stream, type, option: BssomSerializerOptions.Default); ``` ```c# //傳遞一個Type, 非同步的從流中反序列化物件為指定的Type型別 Stream stream = remote(); Type type = typeof(class); object value = await BssomSerializer.DeserializeAsync(stream, type, option: BssomSerializerOptions.Default); ``` ### ReadValue [BssomFieldMarshaller.ReadValue](#bssomfieldmarshaller) 方法用於 在二進位制資料中僅讀取某一個值,如果你只想讀取物件中的某一個值,而不用完整的反序列化它,那麼這個方法非常有用 ```c# //通過內嵌的簡單欄位訪問語言,獲取Dict中的一個Key對應的值 var val = new Dictionary() { { "A",(int)3}, { "B",(DateTime)DateTime.MaxValue}, }; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf("[A]") bsfm.ReadValue(fieldOffInfo).Is(3); ``` ```c# //通過內嵌的簡單欄位訪問語言,獲取class中的一個屬性的值 var val = new MyClass() { Name = "bssom", Nature = "Binary" }; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf("[Name]") bsfm.ReadValue(fieldOffInfo).Is("bssom"); ``` ```c# //通過內嵌的簡單欄位訪問語言,獲取陣列中的一個屬性的值 var val = new object[] { (int)1,(double)2.2 } var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf("$1") bsfm.ReadValue(fieldOffInfo).Is((double)2.2); ``` ```c# //通過內嵌的簡單欄位訪問語言,組合獲取一個物件 var val = new MyClass() { Name = "bssom", Nature = "Binary", Data = new int[] { 3, 2, 1} }; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf("[Data]$1") bsfm.ReadValue(fieldOffInfo).Is(2); ``` ```c# //通過自定義的欄位訪問形式,組合獲取一個物件 var val = new Dictionary() { { DateTime.Parse("2018-01-01"), new object[]{'A','B'} }, { "Charec",(DateTime)DateTime.MaxValue}, }; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); IIndexOfInputSource input = new IndexOfObjectsInputSource(new Entry[]{ new Entry(DateTime.Parse("2018-01-01"),ValueIsMapKey: true), new Entry(1,ValueIsMapKey: false), }) BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf(input) bsfm.ReadValue(fieldOffInfo).Is('B'); ``` ### ReadAllMapKeys [BssomFieldMarshaller.ReadAllMapKeys](#bssomfieldmarshaller) 方法用於 在二進位制資料中讀取Map格式的所有Key和值偏移量,如果你想了解該二進位制資料中的鍵值情況,但又不想完全讀取它,那麼這個方法非常有用. ```c# var val = new Dictionary(){ { "Id" , 1 }, { "Path" , "../t.jpg" }, { "Data" , new byte[3000] } }; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); bsfm.ReadAllMapKeys(BssomFieldOffsetInfo.Zero).Print(); //output // line 1: BssomString::"Id", BssomFieldOffsetInfo // line 2: BssomString::"Path", BssomFieldOffsetInfo // line 3: BssomString::"Data", BssomFieldOffsetInfo ``` ### TryWriteValue [BssomFieldMarshaller.TryWriteValue](#bssomfieldmarshaller) 方法用於 對二進位制資料的值進行修改,當你只想修改物件中的某個值,而不用重新序列化整個物件時,那麼這個方法非常有用 ```c# //修改字串物件 var val = "abcd"; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); bsfm.TryWrite(BssomFieldOffsetInfo.Zero, "abc"); string upVal = BssomSerializer.Deserialize(buf); upVal.Is("abc"); ``` ```c# //修改IDict物件中的某個鍵 var val = new Dictionary(){ { "Id" , 1 }, { "Path" , "../t.jpg" }, { "Data" , new byte[3000] } }; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); bsfm.TryWrite(bsfm.IndexOf("[Id]"), 3); var upVal = BssomSerializer.Deserialize>(buf); upVal["Id"].Is(3); ``` ```c# //修改IDict物件中的某個鍵 var val = new MyClass() { Name = "bssom", Nature = "Binary", Data = new int[] { 3, 2, 1} }; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); bsfm.TryWrite(bsfm.IndexOf("[Name]"), "zz"); var upVal = BssomSerializer.Deserialize(buf); upVal["Name"].Is("zz"); ``` ```c# //修改Array物件中的某個元素 var val = new object[] { "abc" , 37 }; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); bsfm.TryWrite(bsfm.IndexOf("$1"), 40); var upVal = BssomSerializer.Deserialize(buf); ((int)upVal[1]).Is(40); ``` ```c# //組合修改物件中的某個元素 var val = new object[] { 22, 37, new MyClass() { Name = "bssom", Nature = "Binary", Data = new int[] { 3, 2, 1} } }; var buf = BssomSerializer.Serialize(val); var bsfm = new BssomFieldMarshaller(buf); bsfm.TryWrite(bsfm.IndexOf("$2[Name]"), "zz"); var upVal = BssomSerializer.Deserialize(buf); ((MyClass)upVal[1]).Name.Is("zz"); ``` ### [如何使用特性](#9特性) ### [如何定義擴充套件](#5擴充套件) ## 12.如何參與專案貢獻 ### 如果你想參與本專案的發展,那麼我將非常榮幸和高興,歡迎Fork或Pull Request,也可以加入QQ群976304396來進行開源技術的探討 #### 點選加入群聊[.NET開源技術交流群](https://jq.qq.com/?_wv=1027&k=R5cEtIdl) 禁水,只能聊技術 ## 13.誰在使用 * **BssomDB(即將開源)** 一個使用Bssom協議的純C#的嵌入式事務型文件資料庫 ## 14.其它 我喜歡和我一樣的人交朋友,不被環境影響,自己是自己的老師,歡迎加群 Net開源技術交流群 976304396 ,與我討論與效能相關的話題! 想了解我日常是怎樣寫程式碼的嗎? 歡迎關注我的抖音賬號: 198152455 . 作者:小曾 出處:https://www.cnblogs.com/1996V/p/13884968.html 歡迎轉載,但請保留以上完整文章,在顯要地方顯示署名以及原文連結。 Net開源技術交流群 976304396 , 抖音賬號: 198152455