福州seo网站建设/青岛网站seo

福州seo网站建设,青岛网站seo,门户网站的概念,无锡市政建设集团网站文章目录 前言1 项目结构1.1 整体1.2 代码 2 实现2.1 Processor2.1.1 BaseType2.1.2 CollectionType2.1.3 CustomType 2.2 ByteFormatter2.3 ByteHelper 3 使用 前言 ​ BinaryFormatter 类可以将 C# 类对象快速转换为字节数组数据。 ​ 在网络开发时,不会使用 Bi…

文章目录

  • 前言
  • 1 项目结构
    • 1.1 整体
    • 1.2 代码
  • 2 实现
    • 2.1 Processor
      • 2.1.1 BaseType
      • 2.1.2 CollectionType
      • 2.1.3 CustomType
    • 2.2 ByteFormatter
    • 2.3 ByteHelper
  • 3 使用

前言

​ BinaryFormatter 类可以将 C# 类对象快速转换为字节数组数据。

​ 在网络开发时,不会使用 BinaryFormatter 进行数据序列化和反序列化。因为客户端和服务端的开发语言多数情况下不同,BinaryFormatter 序列化的数据无法兼容其它语言。

​ 因此,需要自定义序列化方式。

​ BinaryFormatter 参考链接:2023-05-27 Unity 2进制4——类对象的序列化与反序列化_unity 二进制序列化-CSDN博客。

  • 项目链接:https://github.com/zheliku/ByteHelper。
  • Unity 版本:6000.0.42f1。

1 项目结构

1.1 整体

image-20250321060632469
  • Scenes(示例场景)
  • Scripts(脚本)
    • ByteHelper(工具脚本)
    • Test(测试脚本)

1.2 代码

image-20250321060653045
  • Processor(存放对应类型的 Processor)
    • BaseType(基本类型的 Processor)
    • CollectionType(集合类型的 Processor)
    • CustomType(自定义类型的 Processor)
    • Processor.cs(抽象基类,用于处理对象和字节数组之间的转换)
  • ByteFormatter.cs(存储所有 Processor,并依据类型进行 Write 与 Read)
  • ByteHelper.cs(封装序列化 API)
  • ReflectionExtension.cs(反射方法拓展)

2 实现

2.1 Processor

​ 一个 Processor 用于处理一种类型的序列化,其包含以下 3 种方法:

  • GetBytesLength

    获取对象在字节数组中占用的字节数。

  • Write

    将对象写入 bytes 数组。

  • Read

    从 bytes 数组中读取对象。

// 抽象类 Processor,用于处理对象和字节数组之间的转换
public abstract class Processor
{public abstract int GetBytesLength(object value);public abstract int Write(byte[] bytes, object value, int index);public abstract int Read(byte[] bytes, int index, out object value);
}

​ 使用泛型版本标识每个 Processor 处理的类型:

public abstract class Processor<TValue> : Processor
{public abstract int GetBytesLength(TValue value);public abstract int Write(byte[] bytes, TValue value, int index);public abstract int Read(byte[] bytes, int index, out TValue value);public override int GetBytesLength(object value){return GetBytesLength((TValue) value);}public override int Write(byte[] bytes, object value, int index){return Write(bytes, (TValue) value, index);}public override int Read(byte[] bytes, int index, out object value){int result = Read(bytes, index, out TValue typedValue);value = typedValue;return result;}
}

2.1.1 BaseType

​ 以 int、string 类型为例:

IntProcessor

  • GetBytesLength

    int 类型使用 4 个子节存储,可直接返回 4,也可使用 sizeof(int)

  • Write

    直接转换为子节,写入 bytes 中的 index 位置。返回值为写入后下一处的位置。

  • Read

    直接将 bytes 中 index 位置的数据读取。返回值为读取后下一处的位置。

public class IntProcessor : Processor<int>
{public override int GetBytesLength(int value){return sizeof(int);}public override int Write(byte[] bytes, int value, int index){BitConverter.GetBytes(value).CopyTo(bytes, index);return index + sizeof(int);}public override int Read(byte[] bytes, int index, out int value){value = BitConverter.ToInt32(bytes, index);return index + sizeof(int);}
}

StringProcessor

​ string 类型长度可变,因此需要先写入长度(int 类型),再写入内容。

  • GetBytesLength:int 长度 + 字符串长度。
  • Write:先写入长度,后写入内容。
  • Read:先读取长度,后读取内容。
public class StringProcessor : Processor<string>
{public override int GetBytesLength(string value){return sizeof(int) + value.Length;}public override int Write(byte[] bytes, string value, int index){BitConverter.GetBytes(value.Length).CopyTo(bytes, index);Encoding.UTF8.GetBytes(value).CopyTo(bytes, index + sizeof(int));return index + sizeof(int) + value.Length;}public override int Read(byte[] bytes, int index, out string value){int length = BitConverter.ToInt32(bytes, index);value = Encoding.UTF8.GetString(bytes, index + sizeof(int), length);return index + sizeof(int) + length;}
}

2.1.2 CollectionType

​ 以 ICollectionProcessor 为例,泛型集合类型需要记录集合本身的 Type 与元素的 Type,因此 ICollectionProcessor 具有 2 个泛型参数。

  • GetBytesLength

    集合长度可变,因此也先写入长度,后顺序写入集合元素。

  • Write

    先写入长度,后顺序写入集合元素。

    使用 ByteFormatter.Write 方法,依据元素类型,自动调用对应的 Processor 写入内容。

  • Read

    先读取长度,后顺序读取集合元素。

    使用 ByteFormatter.Read 方法,依据元素类型,自动调用对应的 Processor 读取内容。

public class ICollectionProcessor<TCollection, TValue> : Processor<TCollection> where TCollection : ICollection<TValue>
{public override int GetBytesLength(TCollection value){var length = sizeof(int);foreach (var item in value){length += ByteFormatter.GetBytesLength(item);}return length;}public override int Write(byte[] bytes, TCollection value, int index){int count = value.Count;// 写长度BitConverter.GetBytes(count).CopyTo(bytes, index);index += sizeof(int); // 留 1 个 int 位置用于写长度// 写内容foreach (var item in value){index = ByteFormatter.Write(bytes, item, index);}return index;}public override int Read(byte[] bytes, int index, out TCollection value){// 1. 读取长度(元素数量)int length = BitConverter.ToInt32(bytes, index);index += sizeof(int);// 2. 读取内容value = (TCollection) Activator.CreateInstance(typeof(TCollection));for (int i = 0; i < length; i++){index = ByteFormatter.Read(bytes, index, typeof(TValue), out var item);value.Add((TValue) item);}return index;}
}

2.1.3 CustomType

​ 自定义类型默认序列化所有的字段,且需要添加 [ByteSerializable] 特性。

  • GetBytesLength

    长度可变,因此也先写入长度,后顺序写入字段。

  • Write

    先写入长度,后使用反射获取所有字段信息,依次写入字段内容。

    反射方法 value.GetFieldValues() 在 ReflectionExtension.cs 中封装,默认获取所有字段的值。

    使用 ByteFormatter.Write 方法,依据字段类型,自动调用对应的 Processor 写入。

  • Read

    先读取长度,后使用反射获取所有字段信息,依次读取字段内容。

    反射方法 obj.GetFieldInfos() 在 ReflectionExtension.cs 中封装,默认获取所有字段的信息。

    使用 ByteFormatter.Read 方法,依据字段类型,自动调用对应的 Processor 读取。

    考虑到值类型,在读取时需要装箱,否则反射赋值时,会将内容赋值给 fieldInfo.SetValue 方法中的临时变量。

public class CustomTypeProcessor<TValue> : Processor<TValue>
{public override int GetBytesLength(TValue value){return sizeof(int) + value.GetFieldValues().Sum(v => ByteFormatter.GetBytesLength(v));}public override int Write(byte[] bytes, TValue value, int index){// 先写长度,以便读取BitConverter.GetBytes(GetBytesLength(value)).CopyTo(bytes, index);index += sizeof(int);var fieldValues = value.GetFieldValues();foreach (var fieldValue in fieldValues){index = ByteFormatter.Write(bytes, fieldValue, index);}return index;}public override int Read(byte[] bytes, int index, out TValue value){var obj = (object) Activator.CreateInstance<TValue>(); // 装箱,以防 TValue 为值类型index += sizeof(int);var fieldInfos = obj.GetFieldInfos();foreach (var fieldInfo in fieldInfos){index = ByteFormatter.Read(bytes, index, fieldInfo.FieldType, out var v);fieldInfo.SetValue(obj, v);}value = (TValue) obj; // 拆箱,还原 valuereturn index;}
}

2.2 ByteFormatter

​ 管理所有类型的 Processor,使用字典存储预置基础类型:

public class ByteFormatter
{// 定义字典,用于存储预置类型的处理器public static readonly Dictionary<Type, object> PRIMITIVE_PROCESSORS = new Dictionary<Type, object>(){{ typeof(int), new IntProcessor() },{ typeof(short), new ShortProcessor() },{ typeof(long), new LongProcessor() },{ typeof(float), new FloatProcessor() },{ typeof(double), new DoubleProcessor() },{ typeof(bool), new BoolProcessor() },{ typeof(char), new CharProcessor() },{ typeof(byte), new ByteProcessor() },{ typeof(string), new StringProcessor() },};...
}

​ 依据传入类型,提供对应的 Processor,若不存在则创建。

  1. 在预置类型的字典中,直接读取
public class ByteFormatter
{// 根据类型获取对应的处理器public static Processor<T> GetProcessor<T>(){return (Processor<T>) GetProcessor(typeof(T));}public static Processor GetProcessor(Type type){// 在预置类型的字典中,直接读取if (PRIMITIVE_PROCESSORS.TryGetValue(type, out object value)) {return (Processor) value;}}
}
  1. 优先处理字典类型,并且先处理 KeyValuePair。

    若字典中存在,则直接读取,否则创建 Processor 添加到字典中再返回。

// 如果类型是 KeyValuePair,则使用 KeyValuePairProcessor
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{var processorType = typeof(KeyValuePairProcessor<,>).MakeGenericType(type.GetGenericArguments());var processor     = (Processor) Activator.CreateInstance(processorType);PRIMITIVE_PROCESSORS.Add(type, processor); // 添加到字典中return processor;
}// 如果类型是字典,则使用 IDictionaryProcessor
if (type.IsAssignableToGenericInterface(typeof(IDictionary<,>)))
{var processorType = typeof(IDictionaryProcessor<,,>).MakeGenericType(type, type.GetGenericArguments()[0], type.GetGenericArguments()[1]);var processor     = (Processor) Activator.CreateInstance(processorType);PRIMITIVE_PROCESSORS.Add(type, processor); // 添加到字典中return processor;
}
  1. 对于集合类型与用户自定义类型,同理。

    对于用户自定义类型,需要继承 [ByteSerializable] 特性。也可以根据需要自定义其他方式。

// 如果是集合类型,则使用 ICollectionProcessor
if (type.IsAssignableToGenericInterface(typeof(ICollection<>)))
{var processorType = typeof(ICollectionProcessor<,>).MakeGenericType(type, type.GetGenericArguments()[0]);var processor     = (Processor) Activator.CreateInstance(processorType);PRIMITIVE_PROCESSORS.Add(type, processor); // 添加到字典中return processor;
}// 如果拥有 ByteSerializableAttribute 特性,则使用 CustomTypeProcessor
if (type.HasAttribute<ByteSerializableAttribute>())
{var processorType = typeof(CustomTypeProcessor<>).MakeGenericType(type);var processor     = (Processor) Activator.CreateInstance(processorType);PRIMITIVE_PROCESSORS.Add(type, processor); // 添加到字典中return processor;
}

​ 提供 Write 和 Read 方法,依据参数类型,自动获取对应 Processor 处理。

public static int Write<T>(byte[] bytes, T value, int index)
{return GetProcessor<T>().Write(bytes, value, index);
}public static int Write(byte[] bytes, object value, int index)
{return GetProcessor(value.GetType()).Write(bytes, value, index);
}public static int Read<T>(byte[] bytes, int index, out T value)
{return GetProcessor<T>().Read(bytes, index, out value);
}public static int Read(byte[] bytes, int index, Type type, out object value)
{return GetProcessor(type).Read(bytes, index, out value);
}

2.3 ByteHelper

​ 封装序列化方法。

public class ByteHelper
{public const string EXTENSION = ".bytes";public static string BinarySavePath { get; set; } = Application.persistentDataPath + "/Binary";public static byte[] Serialize<TData>(TData data){var processor = ByteFormatter.GetProcessor<TData>();var bytes     = new byte[processor.GetBytesLength(data)];processor.Write(bytes, data, 0);return bytes;}public static TData Deserialize<TData>(byte[] bytes){ByteFormatter.Read<TData>(bytes, 0, out var value);return value;}public static void SaveBytes<TData>(string filePath, TData data, string extension = EXTENSION){string fullPath = Path.Combine(BinarySavePath, filePath);fullPath = Path.ChangeExtension(fullPath, EXTENSION);var directory = Path.GetDirectoryName(fullPath);if (directory != null && !Directory.Exists(directory)){Directory.CreateDirectory(directory);}byte[] bytes = Serialize(data);File.WriteAllBytes(fullPath, bytes);#if UNITY_EDITORUnityEditor.AssetDatabase.Refresh();
#endif}public static TData LoadBytes<TData>(string filePath, string extension = ByteHelper.EXTENSION){string fullPath = Path.Combine(BinarySavePath, filePath);fullPath = Path.ChangeExtension(fullPath, EXTENSION);if (!File.Exists(fullPath)){ // 不存在文件,则警告,并返回默认值Debug.LogWarning($"ByteHelper: Can't find path \"{fullPath}\"");return default(TData);}byte[] bytes = File.ReadAllBytes(fullPath);return Deserialize<TData>(bytes);}
}

3 使用

​ 以 CustomType 为例,打开 CustomType 场景,点击 “TestScript” 物体,右侧编辑你想要的数据。

image-20250321064638712

​ 运行场景,会自动序列化数据,对应脚本如下:

[Serializable] [ByteSerializable]
public class CustomData
{public  int        Id;private string     _name;public  List<int>  List = new List<int>();public  NestedData NestedData; // Supports nested typespublic string Name{get => _name;set => _name = value;}public override string ToString() {...}
}[Serializable] [ByteSerializable]
public struct NestedData
{public bool Bool;public override string ToString() {...}
}public class Test_CustomType : MonoBehaviour
{public Button Btn;public Text Text;[Header("Set Your CustomData")]public CustomData CustomData;public NestedData NestedData;private void Start(){CustomData.Name = "zheliku"; // 设置私有字段SerializeData();Text.text = "Serialize Data Success!";Btn.onClick.AddListener(OnClick);}// 序列化数据,保存到 CustomType 目录下。private void SerializeData(){ByteHelper.SaveBytes($"CustomType/{nameof(CustomData)}", CustomData);ByteHelper.SaveBytes($"CustomType/{nameof(NestedData)}", NestedData);}// 点击按钮时,显示反序列化数据public void OnClick(){Text.text = "CustomData: " + ByteHelper.LoadBytes<CustomData>($"CustomType/{nameof(CustomData)}") + "\n\n" +"NestedData: " + ByteHelper.LoadBytes<NestedData>($"CustomType/{nameof(NestedData)}");}
}

​ 点击按钮,即可显示反序列化数据:

image-20250321065016604

​ 点击 “ByteHelper/Open Binary Folder” 可打开序列化数据存储目录。

image-20250321065044534

​ CustomType 保存在 “CustomType” 目录下。

image-20250321065216116

​ CustomData 的序列化内容如下:

image-20250321065254361

​ 根据需要可自行扩展 Processor。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/74281.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式项目:利用心知天气获取天气数据实验方案

【实验目的】 1、利用心知天气服务器获取指定位置天气数据 2、将天气数据解析并可视化显示到OLED屏幕 【实验原理】 【实验步骤】 官网注册

LabVIEW FPGA与Windows平台数据滤波处理对比

LabVIEW在FPGA和Windows平台均可实现数据滤波处理&#xff0c;但两者的底层架构、资源限制、实时性及应用场景差异显著。FPGA侧重硬件级并行处理&#xff0c;适用于高实时性场景&#xff1b;Windows依赖软件算法&#xff0c;适合复杂数据处理与可视化。本文结合具体案例&#x…

深度解析 Android Matrix 变换(二):组合变换 pre、post

前言 在上一篇文章中&#xff0c;我们讲解了 Canvas 中单个变换的原理和效果&#xff0c;即缩放、旋转和平移。但是单个旋转仅仅是基础&#xff0c;Canvas 变换最重要的是能够随意组合各种变换以实现想要的效果。在这种情况下&#xff0c;就需要了解如何组合变换&#xff0c;以…

FAQ - VMware vSphere Web 控制台中鼠标控制不了怎么办?

问题描述 在VMware vSphere vCenter Server 的 Web 控制台中新建了一台 Windows Server 2008 R2 虚拟机&#xff0c;但是鼠标进入控制台后&#xff0c;可以看见鼠标光标&#xff0c;但是移动却没有反应。 根因分析 暂无。 解决方案 选中虚拟机>操作>编辑设置>添加新…

关于极端场景下,数据库更新与 MQ 消息一致性保障方案的详细总结

目录 一、核心问题场景 二、RocketMQ 事务消息方案 1. 核心机制 2. 执行流程 3. 关键优势 4. 局限性 三、消息表方案 1. 核心机制 2. 执行流程 3. 关键优势 4. 局限性 四、方案对比与选择 五、实施建议 六、总结 一、核心问题场景 当数据库更新后,若 MQ 消息未…

【redis】主从复制:拓扑结构、原理和psync命令解析

文章目录 拓扑一主一从相关问题 一主多从相关问题 树形主从结构相关问题 主从复制原理复制流程 psync 命令命令解析replicatonidoffset总结 运行流程 拓扑 若干个节点之间按照什么样的方式来进行组织连接 一主一从 都可以读&#xff0c;从节点可以帮主节点分担一部分的压力只…

[RoarCTF 2019]Easy Calc-3.23BUUCTF练习day5(2)

[RoarCTF 2019]Easy Calc-3.23BUUCTF练习day5(2) 解题过程 查看源码 发现calc.php页面&#xff0c;访问一下 分析代码 首先获取$_GET[num]的值并赋给变量$str。然后定义了一个黑名单数组$blacklist&#xff0c;包含了一系列被禁止的字符或转义字符&#xff0c;如空格、制表…

阻塞队列:原理、应用及实现

阻塞队列&#xff1a;原理、应用及实现 什么是阻塞队列以生产消费者模型形象地理解阻塞队列阻塞队列实现生产消费者模型模拟实现阻塞队列实现生产消费者模型 什么是阻塞队列 阻塞队列是一种特殊且实用的队列数据结构&#xff0c;它同样遵循 “先进先出” 的原则。与普通队列不…

【开源宝藏】30天学会CSS - DAY5 第五课 脉冲动画

以下是一个完整的渐进式教程&#xff0c;拆解如何用 HTML CSS 构建“Pulsar”水波脉冲动画。通过阅读&#xff0c;你将理解每个核心属性与关键帧如何配合&#xff0c;让一个小圆不断散发动态波纹&#xff0c;并且文字始终停留在圆心。 第 0 步&#xff1a;项目概览 文件结构示…

2060 裁纸刀

2060 裁纸刀 ⭐️难度&#xff1a;简单 &#x1f31f;考点&#xff1a;2022、规律、思维 &#x1f4d6; &#x1f4da; import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; import java.util.Scanner;public class Main {static int N 100010…

python学习笔记--实现简单的爬虫(一)

任务&#xff1a;爬取豆瓣最受欢迎的250个电影的资料 链接&#xff1a;豆瓣电影 Top 250 用浏览器打开后&#xff0c;使用F12或鼠标右键--检查&#xff0c;查看网页的源代码&#xff0c;分析网页结构&#xff0c;如下图所示&#xff1a; 分析后得知&#xff1a; 1.电影名位于…

Cursor+Claude-3.5生成Android app

一、Android Studio下载 https://developer.android.com/studio?hlzh-tw#get-android-studio 等待安装完成 二、新建工程 点击new project 选择Empty Activity 起一个工程名 当弹出这个框时 可以在settings里面选择No proxy 新建好后如下 点击右边模拟器&#xff0c…

Java EE(15)——网络原理——TCP协议解析一

一.确认应答/(确认)序列号 接收方接收到数据后&#xff0c;向发送方返回一个确认信号(ack)&#xff0c;告诉发送方数据被成功接收。ACK报文段只是作为确认使用的&#xff0c;一般来说不携带应用层数据&#xff08;载荷&#xff09;&#xff0c;也就是说只有报头部分。但有可能…

C#单例模式

单例模式 (Singleton),保证一个类仅有一个实例&#xff0c;并提供一个访问它的全局访问点。通常我们可以让一个全局变量使得一个对象被访问&#xff0c;但它不能防止你实例化对个对象&#xff0c;一个最好的办法就是&#xff0c;让类自身负责保护它的唯一实例。这个类可以保证没…

NO.55十六届蓝桥杯备战|排序|插入|选择|冒泡|堆|快速|归并(C++)

插⼊排序 插⼊排序(Insertion Sort)类似于玩扑克牌插牌过程&#xff0c;每次将⼀个待排序的元素按照其关键字⼤⼩插⼊到前⾯已排好序的序列中&#xff0c;按照该种⽅式将所有元素全部插⼊完成即可 #include <iostream> using namespace std; const int N 1e5 10; …

【Oracle资源损坏类故障】:详细了解坏块

目录 1、物理坏块与逻辑坏块 1.1、物理坏块 1.2、逻辑坏块 2、两个坏块相关的参数 2.1、db_block_checksum 2.2、db_block_checking 3、检测坏块 3.1、告警日志 3.2、RMAN 3.3、ANALYZE 3.4、数据字典 3.5、DBVERIFY 4、修复坏块 4.1、RMAN修复 4.2、DBMS_REPA…

计算机网络高频(二)TCP/IP基础

计算机网络高频(二)TCP/IP基础 1.什么是TCP/IP⭐⭐ TCP/IP是一种网络通信协议,它是互联网中最常用的协议之一。TCP/IP有两个基本的协议:TCP(传输控制协议)和IP(互联网协议)。 TCP(Transmission Control Protocol,传输控制协议)是一种可靠的、面向连接的协议。它负…

【蓝桥杯】12111暖气冰场(多源BFS 或者 二分)

思路 这题可以用BFS做&#xff0c;也可以用二分来做。 用二分这里只提供一个思路&#xff1a;对时间来二分查找&#xff0c;check函数就是检查在特定的时间 t 0 t_0 t0​内每一个暖气炉的传播距离能否覆盖所有格子。 用BFS做&#xff1a; 由几个点开始向外扩散&#xff0c;知道…

DeepSeek自学手册:《从理论(模型训练)到实践(模型应用)》|73页|附PPT下载方法

导 读INTRODUCTION 今天分享是由ai呀蔡蔡团队带来的DeepSeek自学手册&#xff1a;《从理论&#xff08;模型训练&#xff09;到实践&#xff08;模型应用&#xff09;》&#xff0c;这是一篇关于DeepSeek模型训练、应用场景及替代方案的综合指南文章&#xff0c;主要介绍了Deep…

QT软件匠心开发,塑造卓越设计服务

在当今这个数字化飞速发展的时代&#xff0c;软件已经成为我们生活中不可或缺的一部分。而QT&#xff0c;作为一款跨平台的C图形用户界面应用程序开发框架&#xff0c;凭借其强大的功能和灵活性&#xff0c;在众多软件开发工具中脱颖而出。我们深知&#xff0c;在软件开发领域&…