首页 > 编程语言 >C#数据序列化研究:改进版KLV

C#数据序列化研究:改进版KLV

时间:2023-04-11 10:22:48浏览次数:36  
标签:改进版 C# lock int key Offset byte 序列化 public

所谓KLV即Key-Length-Value,以【键-数据长度-数据】的形式将数据序列化成字节流,

这是一种高性能和兼容性的数据序列化方案,,缺点就是用起来很麻烦,

其出现的需求场景如下:

1,硬件和云端的数据交互,最开始是以流的形式顺序写入数据,但是由于版本迭代,数据字段难免出现新增插入更新移除等现象,流式结构加了一大堆版本判定,混乱不堪

3,于是考虑使用Json来序列化数据,但是json性能消耗以及资源占用不甚理想,而且硬件端也没有现成的Json库使用

4,因此模仿Json搞出了一个KLV格式,为每个数据指定一个key,下位机根据key获取数据,解决兼容性问题

该方案的Length比较特殊,由于传输中存在大量基础数值类型,使用4个字节比较浪费,但是1个字节又肯定不够用,Varint又不适用,因此特使用一种特殊的机制来描述Length:当首字节的最高位为0时,用1个字节,用后7位表示数值;当首字节的最高位为1时,用4个字节,用后31位表示数值

不使用Key-Type-Value是因为KLV更简单灵活,传输的时候不用关心数据是什么,用的时候才会根据文档读取指定的key转成具体的数据,当读取到不认识的Key时,可以直接跳过Length,不存在Type的兼容性问题

测试以及对比:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace KLV {
    internal class Test {
        public void Start() {
            for (int i = 0; i < 5; i++) {
                AAA();
            }
        }
        public void AAA() {
            Entity entity = new Entity();
            Stopwatch sw = new Stopwatch();
            sw.Start();
            KLVWriter kw = WriteEntity(entity);
            Console.WriteLine("KLV序列化耗时:" + sw.ElapsedTicks);
            sw.Restart();
            Entity entity_read = Read(kw.Data, kw.StartIndex, kw.Length);
            Console.WriteLine("KLV反序列化耗时:" + sw.ElapsedTicks);
            sw.Restart();
            string json = JsonConvert.SerializeObject(entity);
            Console.WriteLine("Json序列化耗时:" + sw.ElapsedTicks);
            sw.Restart();
            Entity entity1 = JsonConvert.DeserializeObject<Entity>(json);
            Console.WriteLine("Json反序列化耗时:" + sw.ElapsedTicks);
            sw.Restart();
            //通过json字符串对比前后是否一致
            Console.WriteLine(json == JsonConvert.SerializeObject(entity_read));
        }
        Entity Read(byte[] data, int startIndex, int length) {
            KLVReader kr = new KLVReader(data, startIndex, length);
            Entity entity = new Entity {
                P1 = kr.ReadBoolem(1),
                P2 = kr.ReadInt8(2),
                P3 = kr.ReadUInt8(3),
                P4 = kr.ReadInt16(4),
                P5 = kr.ReadUInt16(5),
                P6 = kr.ReadInt32(6),
                P7 = kr.ReadUInt32(7),
                P8 = kr.ReadInt64(8),
                P9 = kr.ReadUInt64(9),
                P10 = kr.ReadFloat(10),
                P11 = kr.ReadDouble(11),
                P12 = kr.ReadString(12),
                P13 = kr.ReadBoolemArray(13),
                P14 = kr.ReadInt8Array(14),
                P15 = kr.ReadUInt8Array(15),
                P16 = kr.ReadInt16Array(16),
                P17 = kr.ReadUInt16Array(17),
                P18 = kr.ReadInt32Array(18),
                P19 = kr.ReadUInt32Array(19),
                P20 = kr.ReadInt64Array(20),
                P21 = kr.ReadUInt64Array(21),
                P22 = kr.ReadFloatArray(22),
                P23 = kr.ReadDoubleArray(23),
                P24 = kr.ReadStringList(24),
                P25 = kr.ReadObject(25, read => {
                    return new EntitySub {
                        P1 = read.ReadInt32(1),
                        P2 = read.ReadDouble(2),
                        P3 = read.ReadString(3)
                    };
                }),
                P26 = kr.ReadObjectList(26, read => {
                    return new EntitySub {
                        P1 = read.ReadInt32(1),
                        P2 = read.ReadDouble(2),
                        P3 = read.ReadString(3)
                    };
                }),
                P27 = kr.ReadDictionary(27, read => {
                    return (read.ReadInt32(1), read.ReadString(2));
                }),
                P28 = kr.ReadDictionary(28, read => {
                    return (read.ReadString(1), read.ReadInt32(2));
                }),
            };
            return entity;
        }
        KLVWriter WriteEntity(Entity entity) {
            byte[] data = new byte[1024];
            int offset = 0;
            KLVWriter kw = new KLVWriter(data, offset);
            kw.WriteBoolem(1, entity.P1);
            kw.WriteInt8(2, entity.P2);
            kw.WriteUInt8(3, entity.P3);
            kw.WriteInt16(4, entity.P4);
            kw.WriteUInt16(5, entity.P5);
            kw.WriteInt32(6, entity.P6);
            kw.WriteUInt32(7, entity.P7);
            kw.WriteInt64(8, entity.P8);
            kw.WriteUInt64(9, entity.P9);
            kw.WriteFloat(10, entity.P10);
            kw.WriteDouble(11, entity.P11);
            kw.WriteString(12, entity.P12);
            kw.WriteBooleanArray(13, entity.P13);
            kw.WriteInt8Array(14, entity.P14);
            kw.WriteUInt8Array(15, entity.P15);
            kw.WriteInt16Array(16, entity.P16);
            kw.WriteUInt16Array(17, entity.P17);
            kw.WriteInt32Array(18, entity.P18);
            kw.WriteUInt32Array(19, entity.P19);
            kw.WriteInt64Array(20, entity.P20);
            kw.WriteUInt64Array(21, entity.P21);
            kw.WriteFloatArray(22, entity.P22);
            kw.WriteDoubleArray(23, entity.P23);
            kw.WriteStringArray(24, entity.P24);
            kw.WriteObject(25, p => {
                p.WriteInt32(1, entity.P25.P1);
                p.WriteDouble(2, entity.P25.P2);
                p.WriteString(3, entity.P25.P3);
            });
            kw.WriteObjects(26, entity.P26, (p, val) => {
                p.WriteInt32(1, val.P1);
                p.WriteDouble(2, val.P2);
                p.WriteString(3, val.P3);
            });
            kw.WriteObjects(27, entity.P27, (p, val) => {
                p.WriteInt32(1, val.Key);
                p.WriteString(2, val.Value);
            });
            kw.WriteObjects(28, entity.P28, (p, val) => {
                p.WriteString(1, val.Key);
                p.WriteInt32(2, val.Value);
            });
            return kw;
        }
    }
}
View Code

序列化写入器:

using System;
using System.Collections.Generic;
using System.Text;

namespace KLV {
    /// <summary>
    /// 一个KLV写入器
    /// </summary>
    public class KLVWriter {
        /// <summary>
        /// 缓冲区
        /// </summary>
        public byte[] Data { get; private set; }
        /// <summary>
        /// 当前KLV开始索引
        /// </summary>
        public int StartIndex { get; private set; }
        /// <summary>
        /// 当前KLV偏移量
        /// </summary>
        public int Offset { get; private set; }
        /// <summary>
        /// 写入数据的长度
        /// </summary>
        public int Length { get { return Offset - StartIndex; } }
        readonly object _lock = new object();
        /// <summary>
        /// 以传入的字节数组为基础,创建一个KLV写入器,从指定索引写数据
        /// </summary>
        /// <param name="data"></param>
        /// <param name="startIndex"></param>
        public KLVWriter(byte[] data, int startIndex) {
            Data = data;
            StartIndex = startIndex;
            Offset = startIndex;
        }
        public void Reset(byte[] data, int startIndex) {
            Data = data;
            StartIndex = startIndex;
            Offset = startIndex;
        }
        /// <summary>
        /// 当已知数据长度时,传入长度,自动计算,减少空间浪费
        /// </summary>
        void WriteLength_Auto(int len) {
            if (len < 0x80) {
                Data[Offset++] = (byte)len; //最高位为0时,用1个字节中的后7位表示数值 
            } else {
                Offset += WriteLength_FourBytes(len, Offset);
            }
        }
        /// <summary>
        /// 写入固定4字节的长度,当先写数据时,可以通过固定4个字节来提高性能
        /// </summary>
        int WriteLength_FourBytes(int len, int offset) {
            len = (int)(len | 0x80000000); //最高位为1时,用4个字节中后31位表示数值
            len.GetBytes().CopyTo(Data, offset);
            return 4;
        }
        public void WriteBoolem(byte key, bool val) {
            WriteUInt8(key, (byte)(val ? 1 : 0));
        }
        public void WriteInt8(byte key, sbyte val) {
            WriteUInt8(key, (byte)val);
        }
        public void WriteUInt8(byte key, byte val) {
            lock (_lock) {
                Data[Offset++] = key;  //key
                WriteLength_Auto(1);  //len
                Data[Offset++] = val;  //val
            }
        }
        void Write(byte key, byte[] bs) {
            lock (_lock) {
                Data[Offset++] = key;  //key
                WriteLength_Auto(bs.Length);  //len
                bs.CopyTo(Data, Offset);  //val
                Offset += bs.Length;
            }
        }
        public void WriteInt16(byte key, short val) {
            Write(key, val.GetBytes());
        }
        public void WriteUInt16(byte key, ushort val) {
            Write(key, val.GetBytes());
        }
        public void WriteInt32(byte key, int val) {
            Write(key, val.GetBytes());
        }
        public void WriteUInt32(byte key, uint val) {
            Write(key, val.GetBytes());
        }
        public void WriteInt64(byte key, long val) {
            Write(key, val.GetBytes());
        }
        public void WriteUInt64(byte key, ulong val) {
            Write(key, val.GetBytes());
        }
        public void WriteFloat(byte key, float val) {
            Write(key, val.GetBytes());
        }
        public void WriteDouble(byte key, double val) {
            Write(key, val.GetBytes());
        }
        public void WriteString(byte key, string val) {
            Write(key, Encoding.UTF8.GetBytes(val));
        }
        public void WriteBooleanArray(byte key, IList<bool> vals) {
            lock (_lock) {
                Data[Offset++] = key;  //key
                WriteLength_Auto(vals.Count);  //len
                foreach (var val in vals) {  //val
                    Data[Offset++] = (byte)(val ? 1 : 0);
                }
            }
        }
        public void WriteInt8Array(byte key, IList<sbyte> vals) {
            lock (_lock) {
                Data[Offset++] = key;
                WriteLength_Auto(vals.Count);
                foreach (var val in vals) {
                    Data[Offset++] = (byte)val;
                }
            }
        }
        public void WriteUInt8Array(byte key, IList<byte> vals) {
            lock (_lock) {
                Data[Offset++] = key;  //key
                WriteLength_Auto(vals.Count);  //len
                vals.CopyTo(Data, Offset);  //val
                Offset += vals.Count;
            }
        }
        public void WriteInt16Array(byte key, IList<short> vals) {
            lock (_lock) {
                Data[Offset++] = key;  //key
                int size = 2;
                WriteLength_Auto(vals.Count * size); //len
                foreach (var val in vals) { //val,为了提高性能,不再采用委托封装
                    val.GetBytes().CopyTo(Data, Offset);
                    Offset += size;
                }
            }
        }
        public void WriteUInt16Array(byte key, IList<ushort> vals) {
            lock (_lock) {
                Data[Offset++] = key;
                int size = 2;
                WriteLength_Auto(vals.Count * size);
                foreach (var val in vals) {
                    val.GetBytes().CopyTo(Data, Offset);
                    Offset += size;
                }
            }
        }
        public void WriteInt32Array(byte key, IList<int> vals) {
            lock (_lock) {
                Data[Offset++] = key;
                int size = 4;
                WriteLength_Auto(vals.Count * size);
                foreach (var val in vals) {
                    val.GetBytes().CopyTo(Data, Offset);
                    Offset += size;
                }
            }
        }
        public void WriteUInt32Array(byte key, IList<uint> vals) {
            lock (_lock) {
                Data[Offset++] = key;
                int size = 4;
                WriteLength_Auto(vals.Count * size);
                foreach (var val in vals) {
                    val.GetBytes().CopyTo(Data, Offset);
                    Offset += size;
                }
            }
        }
        public void WriteInt64Array(byte key, IList<long> vals) {
            lock (_lock) {
                Data[Offset++] = key;
                int size = 8;
                WriteLength_Auto(vals.Count * size);
                foreach (var val in vals) {
                    val.GetBytes().CopyTo(Data, Offset);
                    Offset += size;
                }
            }
        }
        public void WriteUInt64Array(byte key, IList<ulong> vals) {
            lock (_lock) {
                Data[Offset++] = key;
                int size = 8;
                WriteLength_Auto(vals.Count * size);
                foreach (var val in vals) {
                    val.GetBytes().CopyTo(Data, Offset);
                    Offset += size;
                }
            }
        }
        public void WriteFloatArray(byte key, IList<float> vals) {
            lock (_lock) {
                Data[Offset++] = key;
                int size = 4;
                WriteLength_Auto(vals.Count * size);
                foreach (var val in vals) {
                    val.GetBytes().CopyTo(Data, Offset);
                    Offset += size;
                }
            }
        }
        public void WriteDoubleArray(byte key, IList<double> vals) {
            lock (_lock) {
                Data[Offset++] = key;
                int size = 8;
                WriteLength_Auto(vals.Count * size);
                foreach (var val in vals) {
                    val.GetBytes().CopyTo(Data, Offset);
                    Offset += size;
                }
            }
        }
        /// <summary>
        /// 写入一个字符串集合
        /// </summary>
        /// <param name="key"></param>
        /// <param name="vals"></param>
        public void WriteStringArray(byte key, IList<string> vals) {
            lock (_lock) {
                Data[Offset++] = key;
                Offset += 4;
                int startIndex = Offset; //记录数据开始的索引
                foreach (var val in vals) { //字符串比较特殊,异于数值集合和对象集合
                    byte[] bs = Encoding.UTF8.GetBytes(val);
                    WriteLength_Auto(bs.Length); //字符串的长度
                    bs.CopyTo(Data, Offset);
                    Offset += bs.Length;
                }
                WriteLength_FourBytes(Offset - startIndex, startIndex - 4);
            }
        }
        /// <summary>
        /// 写入一个对象,必须在委托函数中写入,目的是限制写入顺序,避免出现数据混乱
        /// </summary>
        /// <param name="key">对象的key</param>
        /// <param name="writer">对象写入器</param>
        public void WriteObject(byte key, Action<KLVWriter> writer) {
            lock (_lock) {
                Data[Offset++] = key;
                var kw = new KLVWriter(Data, Offset + 4); //预留4个字节
                writer(kw); //val
                Offset += WriteLength_FourBytes(kw.Length, Offset); //向预留的4个字节写入数据长度的4个字节
                Offset += kw.Length; //数据的长度
            }
        }
        /// <summary>
        /// 写入一个对象集,必须是同一个类型的对象,内部会循环回调writer,执行相同的写入操作
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">对象的key</param>
        /// <param name="vals">对象集</param>
        /// <param name="writer">每个对象的写入操作</param>
        public void WriteObjects<T>(byte key, IEnumerable<T> vals, Action<KLVWriter, T> writer) {
            lock (_lock) {
                Data[Offset++] = key;
                Offset += 4; //预留4个字节写长度的字节大小
                int startIndex = Offset; //记录数据开始的索引
                KLVWriter kw = new KLVWriter(null, 0); //复用一个写入器对象,提高性能
                foreach (var val in vals) {
                    kw.Reset(Data, Offset + 4); //每个对象单独有个数据长度,也要预留 
                    writer(kw, val);
                    Offset += WriteLength_FourBytes(kw.Length, Offset); //长度的字节个数
                    Offset += kw.Length; //数据的字节个数
                }
                WriteLength_FourBytes(Offset - startIndex, startIndex - 4);
            }
        }
    }
}
View Code

反序列化读取器:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace KLV {
    /// <summary>
    /// KLVV读取器
    /// </summary>
    public class KLVReader {
        /// <summary>
        /// 记录数据相关偏移量的小容器
        /// </summary>
        internal class Block {
            /// <summary>
            /// 数据在缓冲区中的偏移量
            /// </summary>
            public int Offset;
            /// <summary>
            /// 数据的字节长度
            /// </summary>
            public int Length;
            public Block(int offset, int length) {
                Offset = offset;
                Length = length;
            }
        }
        /// <summary>
        /// 解析出来的数据
        /// </summary>
        readonly Dictionary<byte, Block> dict = new Dictionary<byte, Block>();
        /// <summary>
        /// 缓冲区
        /// </summary>
        byte[] Data;
        readonly object _lock = new object();
        /// <summary>
        /// 从缓冲区中指定位置开始指定长度中解析一个KLV结构,并提供读取操作
        /// </summary>
        /// <param name="data"></param>
        /// <param name="startIndex"></param>
        /// <param name="dataLength"></param>
        public KLVReader(byte[] data, int startIndex, int dataLength) {
            Parse(data, startIndex, dataLength);
        }
        KLVReader() { }
        void Parse(byte[] data, int startIndex, int dataLength) {
            dict.Clear();
            Data = data;
            int offset = startIndex;
            int offset_end = offset + dataLength;
            while (offset < offset_end) { //循环解析
                byte key = data[offset++]; //先读取一个字节的key
                (int length, int size) = ReadLenght(data, offset); //在读取len
                offset += size;
                try {
                    dict.Add(key, new Block(offset, length)); //最后读取val
                } catch (Exception ex) {
                    Console.WriteLine(key);
                    throw ex;
                }
                offset += length;
            };
        }
        /// <summary>
        /// 读取KLV中的len,因为KLV采用了2中方式表示len,因此要特殊处理,
        /// 当作为len的首字节的最高位为0,则说明用1个字节表示len,这1个字节的后7位表示最多127个数值,
        /// 当作为len的首字节的最高位为1,则说明用4个字节表示len,这4个字节的后31位标识最多2的31次方的数值
        /// </summary>
        /// <param name="data"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        (int, int) ReadLenght(byte[] data, int offset) {
            int size = 1; //默认认为是1个字节,最高位为0,用一个字节的后7位表示,小于0x80
            int length = data[offset];
            if (length >= 0x80) { //大于等于0x80就认为是用4字节
                length = data.ToInt32(offset);
                length &= 0x7FFFFFFF; //因为最高位固定为1,数值只用了后31位,所以要将最高位置为0
                size = 4;
            }
            return (length, size);
        }
        /// <summary>
        /// 从字典中查找指定key的相关数据
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        /// <exception cref="KLVException"></exception>
        Block GetBlock(byte key) {
            if (!dict.TryGetValue(key, out Block block)) {
                throw new KLVException($"未找到key:{key},data:{BitConverter.ToString(Data)}");
            }
            if (block.Offset + block.Length >= Data.Length) {
                throw new KLVException($"Offset:{block.Offset}+Length:{block.Length}超出了缓冲区大小,data:{BitConverter.ToString(Data)}");
            }
            return block;
        }
        public bool ReadBoolem(byte key) {
            lock (_lock) {
                return Data[GetBlock(key).Offset] == 1;
            }
        }
        public sbyte ReadInt8(byte key) {
            lock (_lock) {
                return (sbyte)Data[GetBlock(key).Offset];
            }
        }
        public byte ReadUInt8(byte key) {
            lock (_lock) {
                return Data[GetBlock(key).Offset];
            }
        }
        public short ReadInt16(byte key) {
            lock (_lock) {
                return Data.ToInt16(GetBlock(key).Offset);
            }
        }
        public ushort ReadUInt16(byte key) {
            lock (_lock) {
                return Data.ToUInt16(GetBlock(key).Offset);
            }
        }
        public int ReadInt32(byte key) {
            lock (_lock) {
                return Data.ToInt32(GetBlock(key).Offset);
            }
        }
        public uint ReadUInt32(byte key) {
            lock (_lock) {
                return Data.ToUInt32(GetBlock(key).Offset);
            }
        }
        public long ReadInt64(byte key) {
            lock (_lock) {
                return Data.ToInt64(GetBlock(key).Offset);
            }
        }
        public ulong ReadUInt64(byte key) {
            lock (_lock) {
                return Data.ToUInt64(GetBlock(key).Offset);
            }
        }
        public float ReadFloat(byte key) {
            lock (_lock) {
                return Data.ToFloat(GetBlock(key).Offset);
            }
        }
        public double ReadDouble(byte key) {
            lock (_lock) {
                return Data.ToDouble(GetBlock(key).Offset);
            }
        }
        public string ReadString(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                return Encoding.UTF8.GetString(Data, block.Offset, block.Length);
            }
        }
        public bool[] ReadBoolemArray(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                bool[] arr = new bool[block.Length];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data[block.Offset + i] == 1;
                }
                return arr;
            }
        }
        public sbyte[] ReadInt8Array(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                sbyte[] arr = new sbyte[block.Length];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = (sbyte)Data[block.Offset + i];
                }
                return arr;
            }
        }
        public byte[] ReadUInt8Array(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                byte[] arr = new byte[block.Length];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data[block.Offset + i];
                }
                return arr;
            }
        }
        public short[] ReadInt16Array(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                int size = 2;
                short[] arr = new short[block.Length / size]; //很多函数这块代码基本一样,但是不用委托封装,稍微提高点性能
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data.ToInt16(block.Offset + i * size);
                }
                return arr;
            }
        }
        public ushort[] ReadUInt16Array(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                int size = 2;
                ushort[] arr = new ushort[block.Length / size];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data.ToUInt16(block.Offset + i * size);
                }
                return arr;
            }
        }
        public int[] ReadInt32Array(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                int size = 4;
                int[] arr = new int[block.Length / size];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data.ToInt32(block.Offset + i * size);
                }
                return arr;
            }
        }
        public uint[] ReadUInt32Array(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                int size = 4;
                uint[] arr = new uint[block.Length / size];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data.ToUInt32(block.Offset + i * size);
                }
                return arr;
            }
        }
        public long[] ReadInt64Array(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                int size = 8;
                long[] arr = new long[block.Length / size];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data.ToInt64(block.Offset + i * size);
                }
                return arr;
            }
        }
        public ulong[] ReadUInt64Array(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                int size = 8;
                ulong[] arr = new ulong[block.Length / size];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data.ToUInt64(block.Offset + i * size);
                }
                return arr;
            }
        }
        public float[] ReadFloatArray(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                int size = 4;
                float[] arr = new float[block.Length / size];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data.ToFloat(block.Offset + i * size);
                }
                return arr;
            }
        }
        public double[] ReadDoubleArray(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                int size = 8;
                double[] arr = new double[block.Length / size];
                for (int i = 0; i < arr.Length; i++) {
                    arr[i] = Data.ToDouble(block.Offset + i * size);
                }
                return arr;
            }
        }
        /// <summary>
        /// 获取解析出来的字符串集合
        /// </summary>
        /// <param name="key">集合的key</param>
        /// <returns></returns>
        public IList<string> ReadStringList(byte key) {
            lock (_lock) {
                var block = GetBlock(key);
                List<string> list = new List<string>(); //字符串长度不固定,因此不知道数量,这里使用集合
                int offset = block.Offset;
                int offset_end = offset + block.Length;
                while (offset < offset_end) {
                    (int len, int size) = ReadLenght(Data, offset); //每个字符串都有一个长度
                    offset += size;
                    list.Add(Encoding.UTF8.GetString(Data, offset, len));
                    offset += len;
                }
                return list;
            }
        }
        /// <summary>
        /// 读取对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">对象的key</param>
        /// <param name="read">对象的读取操作</param>
        /// <returns></returns>
        public T ReadObject<T>(byte key, Func<KLVReader, T> read) where T : class {
            lock (_lock) {
                var block = GetBlock(key);
                return read(new KLVReader(Data, block.Offset, block.Length));
            }
        }
        /// <summary>
        /// 读取对象集,返回一个枚举器
        /// </summary>
        /// <param name="key">对象集的key</param>
        /// <param name="read">每个对象的读取操作</param>
        public IEnumerable<T> ReadObjects<T>(byte key, Func<KLVReader, T> read) {
            lock (_lock) {
                var block = GetBlock(key);
                int offset = block.Offset;
                int offset_end = offset + block.Length;
                KLVReader kr = new KLVReader();
                while (offset < offset_end) {
                    (int len, int size) = ReadLenght(Data, offset);
                    offset += size;
                    kr.Parse(Data, offset, len);  //复用一个kr,提高性能
                    var obj = read(kr);
                    offset += len;
                    yield return obj;
                }
            }
        }
        /// <summary>
        /// 读取对象集
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="read"></param>
        /// <returns></returns>
        public List<T> ReadObjectList<T>(byte key, Func<KLVReader, T> read) {
            List<T> list = new List<T>();
            //return ReadObjects(key,read).ToList(); //为了提高少许的性能,不用上面的枚举器
            lock (_lock) {
                var block = GetBlock(key);
                int offset = block.Offset;
                int offset_end = offset + block.Length;
                KLVReader kr = new KLVReader();
                while (offset < offset_end) {
                    (int len, int size) = ReadLenght(Data, offset);
                    offset += size;
                    kr.Parse(Data, offset, len);  //复用一个kr,提高性能
                    list.Add(read(kr));
                    offset += len;
                }
            }
            return list;
        }
        /// <summary>
        /// 读取一个字典
        /// </summary>
        /// <typeparam name="K"></typeparam>
        /// <typeparam name="V"></typeparam>
        /// <param name="key"></param>
        /// <param name="read"></param>
        /// <returns></returns>
        public Dictionary<K, V> ReadDictionary<K, V>(byte key, Func<KLVReader, (K, V)> read) {
            Dictionary<K, V> dict = new Dictionary<K, V>();
            //foreach (var kv in ReadObjects(key, read)) { //为了提高少许的性能,不用上面的枚举器
            //    dict.Add(kv.Item1,kv.Item2);
            //}
            lock (_lock) {
                var block = GetBlock(key);
                int offset = block.Offset;
                int offset_end = offset + block.Length;
                KLVReader kr = new KLVReader();
                while (offset < offset_end) { //为了稍微提高性能,不对这块进行封装
                    (int len, int size) = ReadLenght(Data, offset);
                    offset += size;
                    kr.Parse(Data, offset, len);  //复用一个kr,提高性能
                    var kv = read(kr);
                    dict.Add(kv.Item1, kv.Item2);
                    offset += len;
                }
            }
            return dict;
        }
    }
}
View Code

测试用的模型及其数据:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace KLV {
    internal class Entity {
        public bool P1 { get; set; } = true;
        public sbyte P2 { get; set; } = -126;
        public byte P3 { get; set; } = 255;
        public short P4 { get; set; } = -1234;
        public ushort P5 { get; set; } = 2345;
        public int P6 { get; set; } = -345678;
        public uint P7 { get; set; } = 45678910;
        public long P8 { get; set; } = -56789101112;
        public ulong P9 { get; set; } = 67891011121314;
        public float P10 { get; set; } = 3.1415f;
        public double P11 { get; set; } = 2.718281828459045;
        public string P12 { get; set; } = "测试啊123";
        public bool[] P13 { get; set; } = new bool[] { true, false, true };
        public sbyte[] P14 { get; set; } = new sbyte[] { -1, 2, -3, 4 };
        public byte[] P15 { get; set; } = { 200, 201, 203 };
        public short[] P16 { get; set; } = new short[] { -17904, 17905, -17906 };
        public ushort[] P17 { get; set; } = new ushort[] { 34567, 45612, 44323 };
        public int[] P18 { get; set; } = new int[] { -1234, 4567, -5678 };
        public uint[] P19 { get; set; } = new uint[] { 1234567, 7890321, 567431 };
        public long[] P20 { get; set; } = new long[] { -44444444, 55555555555, -66666666666 };
        public ulong[] P21 { get; set; } = new ulong[] { 888888888, 99999999 };
        public float[] P22 { get; set; } = new float[] { 2.34f, -45.1f, 78.4f };
        public double[] P23 { get; set; } = new double[] { -123.321, 234.432, -567.897 };
        public IList<string> P24 { get; set; } = new string[] { "测试啊123", "测试啊abc", "测试啊+-*" };
        public EntitySub P25 { get; set; } = new EntitySub() { P1 = 123, P2 = 1.23, P3 = "子实体类测试123" };
        public List<EntitySub> P26 { get; set; } = new List<EntitySub>{
                new EntitySub() { P1 =  1, P2=1.111, P3 = "第一个" },
                new EntitySub() { P1 = 2,P2=2.222, P3 = "第二个"  },
                new EntitySub() { P1 = 3,P2=3.333, P3 = "第三个"  }
            };
        public Dictionary<int, string> P27 { get; set; } = new Dictionary<int, string>() {
                { 1,"aaa"},
                { 2,"bbb"},
                { 3,"ccc"},
            };
        public Dictionary<string, int> P28 { get; set; } = new Dictionary<string, int>() {
                { "aaa",1},
                { "bbb",2},
                { "ccc",3},
            };
    }
}
View Code

子模型:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace KLV {
    internal class EntitySub {
        public int P1 { get; set; }
        public double P2 { get; set; }
        public string P3 { get; set; }
    }
}
View Code

自定义异常:

using System;

namespace KLV {
    /// <summary>
    /// 自定义一个Ktv异常类,便于识别异常类型
    /// </summary>
    public class KLVException : Exception {
        public KLVException(string errMessage) : base(errMessage) { }
    }
}
View Code

用到的基础类型转字节数组的扩展方法:

using System;
using System.Collections.Generic;
using System.Net;

namespace KLV {
    public static class BitConverterExtend {
        /// <summary>
        /// 得到short的字节数组
        /// </summary>
        /// <param name="num"></param>
        /// <param name="bigEndian">是否为大端字节序,默认大端</param>
        /// <returns></returns>
        public static byte[] GetBytes(this short num, bool bigEndian = true) {
            if (bigEndian) {
                num = IPAddress.HostToNetworkOrder(num); //大端网络字节序
            }
            return BitConverter.GetBytes(num);
        }
        /// <summary>
        /// 得到ushort的字节数组
        /// </summary>
        /// <param name="num"></param>
        /// <param name="bigEndian">是否为大端字节序,默认大端</param>
        /// <returns></returns>
        public static byte[] GetBytes(this ushort num, bool bigEndian = true) {
            return ((short)num).GetBytes(bigEndian);  //这样比反转数组快
        }
        /// <summary>
        /// 得到int的字节数组
        /// </summary>
        /// <param name="num"></param>
        /// <param name="bigEndian">是否为大端字节序,默认大端</param>
        /// <returns></returns>
        public static byte[] GetBytes(this int num, bool bigEndian = true) {
            if (bigEndian) {
                num = IPAddress.HostToNetworkOrder(num);
            }
            return BitConverter.GetBytes(num);
        }
        /// <summary>
        /// 得到uint的字节数组
        /// </summary>
        /// <param name="num"></param>
        /// <param name="bigEndian">是否为大端字节序,默认大端</param>
        /// <returns></returns>
        public static byte[] GetBytes(this uint num, bool bigEndian = true) {
            return ((int)num).GetBytes(bigEndian);
        }
        /// <summary>
        /// 得到long的字节数组
        /// </summary>
        /// <param name="num"></param>
        /// <param name="bigEndian">是否为大端字节序,默认大端</param>
        /// <returns></returns>
        public static byte[] GetBytes(this long num, bool bigEndian = true) {
            if (bigEndian) {
                num = IPAddress.HostToNetworkOrder(num);
            }
            return BitConverter.GetBytes(num);
        }
        /// <summary>
        /// 得到ulong的字节数组
        /// </summary>
        /// <param name="num"></param>
        /// <param name="bigEndian">是否为大端字节序,默认大端</param>
        /// <returns></returns>
        public static byte[] GetBytes(this ulong num, bool bigEndian = true) {
            return ((long)num).GetBytes(bigEndian);
        }
        /// <summary>
        /// 得到float的字节数组
        /// </summary>
        /// <param name="num"></param>
        /// <param name="bigEndian">是否为大端字节序,默认大端</param>
        /// <returns></returns>
        public static byte[] GetBytes(this float num, bool bigEndian = true) {
            byte[] arr = BitConverter.GetBytes(num); //本地是小端
            if (bigEndian) {
                arr.ReverseOneself();  //反转为大端
            }
            return arr;
        }
        /// <summary>
        /// 得到double的字节数组
        /// </summary>
        /// <param name="num"></param>
        /// <param name="bigEndian">是否为大端字节序,默认大端</param>
        /// <returns></returns>
        public static byte[] GetBytes(this double num, bool bigEndian = true) {
            byte[] arr = BitConverter.GetBytes(num);
            if (bigEndian) {
                arr.ReverseOneself();
            }
            return arr;
        }
        /// <summary>
        /// 从字节数组总的开始索引开始构建一个short数值
        /// </summary>
        /// <param name="bs">外部传入的字节数组,内部不能更改其数据</param>
        /// <param name="startIndex">开始索引</param>
        /// <param name="bigEndian">是否是大端字节序,默认真</param>
        /// <returns></returns>
        public static short ToInt16(this byte[] bs, int startIndex, bool bigEndian = true) {
            short num = BitConverter.ToInt16(bs, startIndex); //本地是小端
            if (bigEndian) {
                num = IPAddress.NetworkToHostOrder(num); //转为大端
            }
            return num;
        }
        /// <summary>
        /// 从字节数组总的开始索引开始构建一个ushort数值
        /// </summary>
        /// <param name="bs">外部传入的字节数组,内部不能更改其数据</param>
        /// <param name="startIndex">开始索引</param>
        /// <param name="bigEndian">是否是大端字节序,默认真</param>
        /// <returns></returns>
        public static ushort ToUInt16(this byte[] bs, int startIndex, bool bigEndian = true) {
            return (ushort)bs.ToInt16(startIndex, bigEndian); //因为没有IPAddress.NetworkToHostOrder,所以直接调用ToInt16转换
        }
        /// <summary>
        /// 从字节数组总的开始索引开始构建一个int数值
        /// </summary>
        /// <param name="bs">外部传入的字节数组,内部不能更改其数据</param>
        /// <param name="startIndex">开始索引</param>
        /// <param name="bigEndian">是否是大端字节序,默认真</param>
        /// <returns></returns>
        public static int ToInt32(this byte[] bs, int startIndex, bool bigEndian = true) {
            int num = BitConverter.ToInt32(bs, startIndex);
            if (bigEndian) {
                num = IPAddress.NetworkToHostOrder(num);
            }
            return num;
        }
        /// <summary>
        /// 从字节数组总的开始索引开始构建一个uint数值
        /// </summary>
        /// <param name="bs">外部传入的字节数组,内部不能更改其数据</param>
        /// <param name="startIndex">开始索引</param>
        /// <param name="bigEndian">是否是大端字节序,默认真</param>
        /// <returns></returns>
        public static uint ToUInt32(this byte[] bs, int startIndex, bool bigEndian = true) {
            return (uint)bs.ToInt32(startIndex, bigEndian);
        }
        /// <summary>
        /// 从字节数组总的开始索引开始构建一个long数值
        /// </summary>
        /// <param name="bs">外部传入的字节数组,内部不能更改其数据</param>
        /// <param name="startIndex">开始索引</param>
        /// <param name="bigEndian">是否是大端字节序,默认真</param>
        /// <returns></returns>
        public static long ToInt64(this byte[] bs, int startIndex, bool bigEndian = true) {
            long num = BitConverter.ToInt64(bs, startIndex);
            if (bigEndian) {
                num = IPAddress.NetworkToHostOrder(num);
            }
            return num;
        }
        /// <summary>
        /// 从字节数组总的开始索引开始构建一个ulong数值
        /// </summary>
        /// <param name="bs">外部传入的字节数组,内部不能更改其数据</param>
        /// <param name="startIndex">开始索引</param>
        /// <param name="bigEndian">是否是大端字节序,默认真</param>
        /// <returns></returns>
        public static ulong ToUInt64(this byte[] bs, int startIndex, bool bigEndian = true) {
            return (ulong)(bs.ToInt64(startIndex, bigEndian));
        }
        /// <summary>
        /// 从字节数组总的开始索引开始构建一个float数值
        /// </summary>
        /// <param name="bs">外部传入的字节数组,内部不能更改其数据</param>
        /// <param name="startIndex">开始索引</param>
        /// <param name="bigEndian">是否是大端字节序,默认真</param>
        /// <returns></returns>
        public static float ToFloat(this byte[] bs, int startIndex, bool bigEndian = true) {
            if (bigEndian) {
                return BitConverter.ToSingle(bs.ReverseCopy(startIndex, 4), 0);
            }
            return BitConverter.ToSingle(bs, startIndex);
        }
        /// <summary>
        /// 从字节数组总的开始索引开始构建一个double数值
        /// </summary>
        /// <param name="bs">外部传入的字节数组,内部不能更改其数据</param>
        /// <param name="startIndex">开始索引</param>
        /// <param name="bigEndian">是否是大端字节序,默认真</param>
        /// <returns></returns>
        public static double ToDouble(this byte[] bs, int startIndex, bool bigEndian = true) {
            if (bigEndian) {
                return BitConverter.ToDouble(bs.ReverseCopy(startIndex, 8), 0);
            }
            return BitConverter.ToDouble(bs, startIndex);
        }
        /// <summary>
        /// 反转传入的数组的数据
        /// </summary>
        public static void ReverseOneself(this IList<byte> bytes) {
            int len = bytes.Count / 2;
            for (int i = 0; i < len; i++) {
                (bytes[bytes.Count - 1 - i], bytes[i]) = (bytes[i], bytes[bytes.Count - 1 - i]);
            }
        }
        /// <summary>
        /// 从bytes中反转拷贝数据,不破坏原bytes的数据
        /// </summary>
        public static byte[] ReverseCopy(this IList<byte> bytes, int startIndex, int length) {
            byte[] arr = new byte[length];
            for (int i = 0; i < length; i++) {
                arr[i] = bytes[startIndex + length - 1 - i];
            }
            return arr;
        }
    }
}
View Code

 

标签:改进版,C#,lock,int,key,Offset,byte,序列化,public
From: https://www.cnblogs.com/luludongxu/p/17303934.html

相关文章

  • Javascript 原型与原型链
    在BrendanEich设计Javascript时,借鉴了Self和Smalltalk这两门基于原型的语言。之所以选择基于原型的面向对象系统,是因为BrendanEich一开始没有打算在JavaScript中加入类的概念,其设计初衷是为非专业的开发人员提供一个方便的工具,使其使用尽可能简单、易学。随着人们对网页要求的逐......
  • Nginx 通过 Cookie 做灰度就这么简单
    由于业务需要,要做灰度发布测试,刚开始考虑通过IP来做判断,分发不同的后端,但是由于IP不好确定,有的客户端IP不固定,所以考虑用cookie来做逻辑很简单,如下图:  在nginx做这个很简单,两个方法,map或if首先看map怎么做为了模拟环境,我又不想多开服务器,所以起了个http......
  • The Cross-Entropy Loss Function for the Softmax Function
    TheCross-EntropyLossFunctionfortheSoftmaxFunction作者:凯鲁嘎吉-博客园 http://www.cnblogs.com/kailugaji/本文介绍含有softmax函数的交叉熵损失函数的求导过程,并介绍一种交叉熵损失的等价形式,从而解决因log(0)而出现数值为NaN的问题。1.softmax函数求导2.交......
  • dotnet-exec 让 C# 程序更简单【转】
    Introdotnet-exec是一个可以执行C#程序而不需要项目文件的命令行工具,并且你可以指定自定义的入口方法不仅仅是Main方法在python/NodeJs里,可以使用pythonxx.py/nodexx.js来运行代码,在C#中一般是需要项目文件才能dotnetrun运行代码,这对于一些比较简单的代码来说会显得麻......
  • ubuntu 编译出现错误fatal error: bits/libc-header-start.h: No such file or direct
    在ubuntugcc编译程序出现错误fatalerror:bits/libc-header-start.h:Nosuchfileordirectory表明缺少库环境。解决方法aptupdateapt-getinstallgcc-multilib成功编译......
  • python3写csv中文文件,可以直接excel打开
    写出python3代码:将如下数据转为windowsexcel文件。 importcsvdata=[[1010205,'2022/11/23','R染(Inception)攻击','T89','在远程系统的启动文件登录后可以自动执行恶意脚本或可执行文件。','例:copyrogramData\Microsoft\W\Programs\StartUp',4,85,......
  • git pull时,提示Your local changes to the following files would be overwritten by
    问题描述:本地修改了代码后,执行“gitpull”命令时,无法更新代码,并报错提示:“Yourlocalchangestothefollowingfileswouldbeoverwrittenbymerge” 问题原因:是因为本地修改的代码与git服务器的代码冲突导致。如果不冲突,会自动更新合并代码。 gitpull冲突的解决办......
  • dataclass
    [数据类(dataclass)](Python3.7+中的数据类(指南)–真正的Python(realpython.com))目录引入数据类的替代项数据类基础默认值类型提示添加方法更灵活的数据类高级默认值数据类的字符串表示卡片比较不可变数据类继承优化数据类引入数据类是通常主要包含数据的类,尽管实际上......
  • Rocky Linux 9 Wazuh 部署
    1、DockerCE安装参考:https://www.cnblogs.com/a120608yby/p/9883175.html2、DockerCompose安装参考:https://www.cnblogs.com/a120608yby/p/14582853.html3、主机参数优化#编辑/etc/sysctl.conf#vim/etc/sysctl.conf...vm.max_map_count=262144...#使配置......
  • python写入数据到oracle clob字段
     环境:Python:3.6.5  #!/usr/bin/envpython#coding=utf-8importos,json,urllib,datetime,shutilimporttimeimportcx_Oraclegl_mysql_server="192.168.1.118"gl_user_name="hxl"gl_password="mysql"gl_db_name="db_t......