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


好久沒更新博客了,我開源了一款高性能的二進制序列化器Bssom.Net和新穎的二進制協議Bssom,歡迎大家Star,歡迎參與項目貢獻!

Net開源技術交流群 976304396,禁止水,只能討論技術, 歡迎與我討論和性能相關的技術話題!
另外,我還在抖音申請了一個賬號,用來記錄自己的日常生活, 想了解我平常是怎么寫代碼的嗎? 來關注我一下,哈哈! 抖音號: 198152455
Bssom.Net項目地址: https://github.com/1996v/Bssom.Net
Bssom協議地址: https://github.com/1996v/Bssom

A small, high performance, powerful serializer using bssom binary protocol

Nuget

Bssom.Net是一個使用bssom結構協議實現的高性能結構化二進制序列化器,它具有以下特點,小巧,快速,功能性強.

  1. 小巧,文件僅300多k
  2. 快速,它具有一流的序列化和反序列化性能
  3. 功能性強:
    • 可以獲取對象被序列化后的大小而不用完整序列化對象
    • 可以讀取對象中的某個元素而不用完整的反序列化對象
    • 可以更改對象中的某個元素而不用完整的序列化
    • 序列化后的格式具有自描述性

為什么需要?

目前c#已經有很多二進制序列化器, 但這些序列化器都只提供了單一的序列化和反序列化功能.

Bssom.Net采取了Bssom協議, 使序列化后的數據具有結構化特性, 且擁有直接對字段進行編組的功能, 這使得Bssom.Net能做到其它序列化器所達不到的事情.

  • 當我想在序列化對象時知道對象被序列化后的大小, 以提前來選擇該對象應該被序列化的正確位置(如數據庫引擎的FSM算法), 那么Bssom.Net能夠滿足你
  • 當我擁有一個大的二進制數據, 但是我只想無合約的讀取其中一個字段, 以避免完整的反序列化開銷, 那么Bssom.Net能夠滿足你
  • 當我擁有一個已經被序列化后的數據包, 我只想無合約的修改其中一個字段, 以避免重新序列化的開銷, 那么Bssom.Net能夠滿足你
  • 當我想讓對象被序列化后仍能保留類型信息, 而不用依賴實體, 那么Bssom.Net能夠滿足你

什么是Bssom協議?

Bssom(Binary search algorithm structure model object binary marshalling)是一個使用二分查找算法模型對對象進行結構化編組的協議,被編組后的數據具有特殊的元數據信息,根據這些元數據信息可以高效的僅讀取和更改對象中的某個元素,這樣可以在對大對象進行序列化和反序列化的過程中不必因為只讀取或只寫入一個字段而造成完整的序列化開銷。

大綱

1.性能


這里是與.NET平台下非常優秀的兩款序列化程序(MessagePackProtobuf-net)進行性能比較的基准.

柱狀數據代表執行相同任務所花費的時間, 越低代表性能越快, 折線數據代表執行相同任務所產生的GC, 越低代表在執行中所產生的垃圾越少 , 從性能比較結果可以看出Bssom.Net的性能是非常優異的.

Bssom.Net使用很多技術來提高性能.

  • 使用內存池技術, 用於寫入的內存可以復用
  • 使用表達式和Emit動態編程技術, 對類型進行了特殊處理, 且避免值類型裝箱拆箱
  • 使用泛型靜態緩存, 避免了字典查找開銷
  • 包裝了異常拋出代碼, 以增加內聯的可能性
  • 更多的對強類型進行調用, 而不是接口抽象
  • 預處理Map2類型的元數據, 在序列化時不需要對其進行再次編碼
  • 在查找Map2鍵時, 提前固定局部引用, 而不是標准函數調用
  • 解析Map1類型時, 自動構建8字節的自動機跳躍查找
  • 值得一提的是, 出於減少依賴, 減少體積的目的, Bssom.Net並沒有依賴System.Memory.dll, 因此無法使用Span<T>,Memory<T>等類型, 這意味着Bssom.Net的實現將無法使用ByReference<T>這一JIT內部特性, 因此目前的讀寫器將不具備讀寫局部化和去虛擬化及內聯調用的這三個性能優化點 ( 但即使這樣, 目前的Bssom.Net性能依然非常優秀 ) , 若將來有可能支持Span<T>類型的話, 那么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進行了IBssomBufferIBssomBufferWriter接口的封裝, 用戶無需手動封裝

3.格式化器

格式化是Bssom.Net將.Net對象和Bssom格式進行互相轉換的一個過程. Bssom.Net通過IBssomFormatter<T>來實現對對象的格式化.

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中對於鍵值對定義的行為規則, 為滿足該規則的對象進行動態解析代碼的生成.在解析器內部, 將通過運行時的配置選項來選擇Map1Map2的兩種格式
ICollectionResolver 獲取和生成具有IColloction行為的類型的解析器, 該解析器抽象了BCL中對於收集器定義的行為規則, 為滿足該規則的對象進行動態解析代碼的生成. 在解析器內部, 如果集合中的元素類型為基元類型, 則將其解析成Array1格式, 否則解析為Array2格式
MapCodeGenResolver 獲取和生成對象的公開字段和屬性進行BssomMap類型編碼的解析器, 若對象為接口, 則會自動生成該接口的實現作為反序列化的載體.在解析器內部, 始終將類型解析為Map2格式, 且提供Map1Map2兩種格式的反序列化代碼
ObjectResolver 提供了Object類型的解析器
CompositedResolver 復合解析器,組合了Object,Primitive,Attribute,BssomValue,BuildIn,IDictionary,ICollection,MapCodeGen解析器

因為IDictionaryResolverICollectionResolver中定義的足夠抽象的規則,Bssom.Net不需要為未來.NET可能出現的新的IDictionaryIColloction實現而編寫特定的解析代碼.

在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內部定義的解析器或格式化器不滿意的話, 則可以自己擴展它.

用戶可以自己通過實現IFormatterResolverIBssomFormatter替代默認的解析器, 在Bssom.Serializer.Binary.BssomBinaryPrimitives(在即將到來的小版本中將重構該類)和讀寫器本身所暴露的公開API中提供對Bssom格式的低級寫入和讀取實現.

簡單示例可以參考更多可能介紹

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協議有着良好的結構化特征, 在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只能是整數類型

假設有如下數據

{
   "Postcodes" : {   
		  "WuHan" : [430070,430071,430072,430073],
		  "XiangYang" : [441000,441001,441002]
		},
   "Province" : "HuBei"
}

可以通過如下方式進行元素訪問, 在示例中可以了解更多細節

[Postcodes][WuHan]$1  => 4330071
[Province]  => "HuBei"

自定義字段訪問形式接口

Bssom.Net為IndexOf提供了IIndexOfInputSource接口用來接收自定義的字段訪問源, 使用該接口后Map類型的Key將不再受限制, Key可以為任意輸入類型.

IndexOfObjectsInputSource 是 Bssom.Net為用戶提供的IIndexOfInputSource接口的通用實現. 它接收一組可迭代的對象,當調用IndexOf的時候, 將依次對對象進行迭代.

假設有如下數據

{
   2018-01-01 : {
         0 : ["Rain1","Rain2","Rain3"],
         4 : ["Rain4","Fair5","Fair6"]   
    }
}

可以通過如下方式進行元素訪問, 在示例中可以了解更多細節

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()方法你將看到這些自動生成的代碼.

MapCodeGenResolver中對Map2類型的反序列化則使用了內置的Bssom協議的Map格式查找代碼,該代碼是狀態機模式編寫, 分為快速和低速版, 這取決於讀取器是否能夠提供 TryReadFixedRef.

另外,對於Size方法,MapCodeGenResolver的處理也是非常快速的,因為它已經提前計算好了元數據的大小,並且內聯了基元字段本身的固定大小.

9.特性

Bssom.Net中目前擁有5個特性.

  • AliasAttribute : 別名特性, 用於修改Map格式對象字段在二進制中所保存的字段名稱
  • BssomFormatterAttribute : 自定義格式化特性, 當字段屬性或類型被該特性標記后, 此類型的格式化將采用該特性所指定的格式化器
  • IgnoreKeyAttribute : 忽略某一個Key, 序列化時將忽略被標記的字段, 適用於Map格式
  • OnlyIncludeAttribute : 僅包含某一個Key, 序列化時僅包含該Key, 適用於Map格式, 與IgnoreKeyAttribute作用相反,優先級更高
  • SerializationConstructorAttribute : 為類型的反序列化指定一個構造函數

10.更多的可能性

你可以自己編寫解析器, 編寫格式化器, 也可以定義你自己的特性, 也可以封裝用於序列化的Option, 並且Bssom.Net還提供了上下文數據槽的支持, 這可以讓序列化行為變得多樣性.

如果你能為Bssom.Net提供有用或者側重於高性能的擴展包, 那么請您告訴我.

下面示例編寫了以String類型為原型的解析器, 該解析器通過與上下文交互的方式來帶來字符串類型序列化性能的提升.

public sealed class MyStringFormatterResolver : IFormatterResolver
{
    public static MyStringFormatterResolver Instance = new MyStringFormatterResolver();

    public IBssomFormatter<T> GetFormatter<T>()
    {
        return FormatterCache<T>.Formatter;
    }

    private static class FormatterCache<T>
    {
        public static readonly IBssomFormatter<T> Formatter;

        static FormatterCache()
        {
            if (typeof(T) == typeof(string))
                Formatter = (IBssomFormatter<T>)(object)MyStringFormatter.Instance;
        }
    }
}

public sealed class MyStringFormatter : IBssomFormatter<string>
{
    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;
    }
}
public void MyTest()
{
     var option = BssomSerializerOptions.Default.WithFormatterResolver(MyStringFormatterResolver.Instance);
     string str = RandomHelper.RandomValue<string>();
     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<string>(bytes);

     Assert.Equal(str,deStr);
}

上面的代碼是單獨為String定義了一個新的解析器和新的格式化器, 該格式化器可以將Size方法中對字符串計算的UTF8大小存儲在上下文中, 這樣在序列化時不用重復對String再做一次UTF8大小計算.

11.如何使用

Bssom.Net是無合約的, 開箱即用, 這里有些示例代碼.

Size

BssomSerializer.Size 方法用於 獲取對象被序列化后的二進制數據大小,高性能的內部實現,幾乎無開銷

//獲取值被序列化后的大小
object value = RandomHelper.RandomValue<object>();
int size = BssomSerializer.Size(value, option: BssomSerializerOptions.Default);
//使用上下文獲取值被序列化后的大小
BssomSizeContext context = new BssomSizeContext(BssomSerializerOptions.Default);
object value = RandomHelper.RandomValue<object>();
int size = BssomSerializer.Size(ref context, value);

Serialize

BssomSerializer.Serialize 方法用於 將給定的值序列化為Bssom二進制,高性能的內部實現,以下是部分常用方法,每個方法都擁有CancellationToken的重載

//直接對對象進行序列化,將返回一個被序列化后的字節數組
object value = RandomHelper.RandomValue<object>();
byte[] binary = BssomSerializer.Serialize(value, option: BssomSerializerOptions.Default);
//將對象序列化到指定的字節數組中,若容量不夠將自動擴容,最終返回序列化的字節數
object value = RandomHelper.RandomValue<object>();
byte[] buf = local();
int serializeSize = BssomSerializer.Serialize(ref buf, 0, value, option: BssomSerializerOptions.Default);
//將對象序列化到自定義的寫入器中
object value = RandomHelper.RandomValue<object>();
IBssomBufferWriter writer = new Impl();
BssomSerializer.Serialize(value, writer, option: BssomSerializerOptions.Default);
//使用序列化上下文進行序列化
object value = RandomHelper.RandomValue<object>();
BssomSerializeContext context = new BssomSerializeContext(BssomSerializerOptions.Default);
byte[] binary = BssomSerializer.Serialize(ref context, value);
//將對象序列化到流中
object value = RandomHelper.RandomValue<object>();
Stream stream = new MemoryStream();
BssomSerializer.Serialize(stream, value, option: BssomSerializerOptions.Default);
//異步的將對象序列化到流中
object value = RandomHelper.RandomValue<object>();
Stream stream = new MemoryStream();
await BssomSerializer.SerializeAsync(stream, value, option: BssomSerializerOptions.Default);

Deserialize

BssomSerializer.Deserialize 方法用於 將給定的Bssom緩沖區反序列化為對象,高性能的內部實現,以下是部分常用方法,每個方法都擁有CancellationToken的重載

//從給定的字節數組中反序列化對象
byte[] buf = remote();
T value = BssomSerializer.Deserialize<T>(buf, 0, out int readSize, option: BssomSerializerOptions.Default);
//從給定的buffer中反序列化對象
IBssomBuffer buffer = remote();
object value = BssomSerializer.Deserialize<object>(buffer, option: BssomSerializerOptions.Default);
//使用上下文從給定的buffer中反序列化對象
BssomDeserializeContext context = new BssomDeserializeContext(BssomSerializerOptions.Default);
IBssomBuffer buffer = remote();
object value = BssomSerializer.Deserialize<object>(ref context, buffer);
//從流中反序列化對象
Stream stream = remote();
object value = BssomSerializer.Deserialize<object>(stream, option: BssomSerializerOptions.Default);
//異步的從流中反序列化對象
Stream stream = remote();
object value = await BssomSerializer.DeserializeAsync<object>(stream, option: BssomSerializerOptions.Default);
//傳遞一個Type, 從流中反序列化對象為指定的Type類型
Stream stream = remote();
Type type = typeof(class);
object value = BssomSerializer.Deserialize(stream, type, option: BssomSerializerOptions.Default);
//傳遞一個Type, 異步的從流中反序列化對象為指定的Type類型
Stream stream = remote();
Type type = typeof(class);
object value = await BssomSerializer.DeserializeAsync(stream, type, option: BssomSerializerOptions.Default);

ReadValue

BssomFieldMarshaller.ReadValue 方法用於 在二進制數據中僅讀取某一個值,如果你只想讀取對象中的某一個值,而不用完整的反序列化它,那么這個方法非常有用

//通過內嵌的簡單字段訪問語言,獲取Dict中的一個Key對應的值
var val = new Dictionary<string, object>() {
            { "A",(int)3},
            { "B",(DateTime)DateTime.MaxValue},
        };
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf("[A]")
bsfm.ReadValue<int>(fieldOffInfo).Is(3);
//通過內嵌的簡單字段訪問語言,獲取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<string>(fieldOffInfo).Is("bssom");
//通過內嵌的簡單字段訪問語言,獲取數組中的一個屬性的值
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<double>(fieldOffInfo).Is((double)2.2);
//通過內嵌的簡單字段訪問語言,組合獲取一個對象
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<int>(fieldOffInfo).Is(2);
//通過自定義的字段訪問形式,組合獲取一個對象
var val = new Dictionary<object, object>() {
            { 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<int>(fieldOffInfo).Is('B');

ReadAllMapKeys

BssomFieldMarshaller.ReadAllMapKeys 方法用於 在二進制數據中讀取Map格式的所有Key和值偏移量,如果你想了解該二進制數據中的鍵值情況,但又不想完全讀取它,那么這個方法非常有用.

var val = new Dictionary<object, object>(){
           { "Id" , 1 },
           { "Path" , "../t.jpg" },
           { "Data" , new byte[3000] }
};
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
bsfm.ReadAllMapKeys<object>(BssomFieldOffsetInfo.Zero).Print();
//output
//  line 1: BssomString::"Id", BssomFieldOffsetInfo
//  line 2: BssomString::"Path", BssomFieldOffsetInfo
//  line 3: BssomString::"Data", BssomFieldOffsetInfo

TryWriteValue

BssomFieldMarshaller.TryWriteValue 方法用於 對二進制數據的值進行修改,當你只想修改對象中的某個值,而不用重新序列化整個對象時,那么這個方法非常有用

//修改字符串對象
var val = "abcd";
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
bsfm.TryWrite(BssomFieldOffsetInfo.Zero, "abc");
string upVal = BssomSerializer.Deserialize<string>(buf);
upVal.Is("abc");
//修改IDict對象中的某個鍵
var val = new Dictionary<string, object>(){
           { "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<Dictionary<string, object>>(buf);
upVal["Id"].Is(3);
//修改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<MyClass>(buf);
upVal["Name"].Is("zz");
//修改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<MyClass>(buf);
((int)upVal[1]).Is(40);
//組合修改對象中的某個元素
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<MyClass>(buf);
((MyClass)upVal[1]).Name.Is("zz");

如何使用特性

如何定義擴展

12.如何參與項目貢獻

如果你想參與本項目的發展,那么我將非常榮幸和高興,歡迎Fork或Pull Request,也可以加入QQ群976304396來進行開源技術的探討

點擊加入群聊.NET開源技術交流群 禁水,只能聊技術

13.誰在使用

  • BssomDB(即將開源) 一個使用Bssom協議的純C#的嵌入式事務型文檔數據庫

14.其它

我喜歡和我一樣的人交朋友,不被環境影響,自己是自己的老師,歡迎加群 Net開源技術交流群 976304396 ,與我討論與性能相關的話題!
想了解我日常是怎樣寫代碼的嗎? 歡迎關注我的抖音賬號: 198152455 .

作者:小曾
出處:https://www.cnblogs.com/1996V/p/13884968.html 歡迎轉載,但請保留以上完整文章,在顯要地方顯示署名以及原文鏈接。
Net開源技術交流群 976304396 , 抖音賬號: 198152455


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM