字段临时缓存包装器
前言
在实际开发中,我们有时候存在一种需求,例如对于某个字段,我们希望在某个明确的保存节点前对字段的修改都仅作为缓存保留,最终是否应用这些修改取决于某些条件,比如玩家对游戏设置的修改可能需要玩家明确确认应用修改后才会保存下来,在此之前玩家在游戏界面上的所有修改都是临时的。
本文基于这个需求探索出了一种解决方案“字段临时缓存包装器”,通过创建字段或用于存储字段临时数据的数据结构的副本来实现临时缓存,虽然我们同样可以采用直接声明一个副本字段的方式来达到同样的目的,但是这可能会增加冗余代码,且不利于代码的维护,通过包装器来封装临时缓存的通用逻辑,与具体业务逻辑隔离。
代码
v1.0
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;/// <summary>
/// 临时包装器
/// </summary>
/// <typeparam name="T">字段类型</typeparam>
/// <remarks>
/// 该类主要用于创造某个字段的副本作为该字段的临时缓存,避免直接修改源字段。
/// </remarks>
public class TempWrapper<T> : IDisposable
{/// <summary>/// 是否为值类型/// <para>提示:若为true则表示包装字段为值类型,否则为引用类型</para>/// </summary>public static bool isValueType => _isValueType;/// <summary>/// 缓存字段/// <para>提示:对于值类型而言,该属性涉及拷贝</para>/// </summary>public T value{get{if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");return _value;}set{if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");_value = value;}}/// <summary>/// 获取引用/// <para>提示:对于值类型而言,该属性直接返回引用从而避免拷贝</para>/// </summary>public ref T refrence{get{if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");return ref _value;}}/// <summary>/// 是否已经释放/// </summary>public bool isDisposed => _isDisposed;static readonly bool _isValueType = typeof(T).IsValueType;static readonly bool _isDisposable = typeof(IDisposable).IsAssignableFrom(typeof(T));static readonly object _key = new object();T _value;bool _isDisposed;TempWrapper() { }/// <summary>/// 包装指定字段并返回包装类/// </summary>/// <param name="value">待包装字段的引用</param>/// <remarks>/// <para>提示:采用二进制序列化和反序列化生成字段副本</para>/// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>/// </remarks>public static TempWrapper<T> WrapByBinary(ref T value){lock (_key){try{TempWrapper<T> wrapper = new TempWrapper<T>();if (_isValueType) wrapper._value = value;else{using (MemoryStream ms = new MemoryStream()){IFormatter formatter = new BinaryFormatter();formatter.Serialize(ms, value);ms.Seek(0, SeekOrigin.Begin);wrapper._value = (T)formatter.Deserialize(ms);}}return wrapper;}catch (Exception e){throw new InvalidOperationException("Failed to wrap.", e);}}}/// <summary>/// 包装指定字段并返回包装类/// <para>提示:采用JSON序列化和反序列化生成字段副本</para>/// </summary>/// <param name="value">待包装字段的引用</param>public static TempWrapper<T> WrapByJson(ref T value){lock (_key){try{TempWrapper<T> wrapper = new TempWrapper<T>();if (_isValueType) wrapper._value = value;else{string jsonStr = JsonConvert.SerializeObject(value);wrapper._value = JsonConvert.DeserializeObject<T>(jsonStr);}return wrapper;}catch (Exception e){throw new InvalidOperationException("Failed to wrap.", e);}}}/// <summary>/// 包装生成器所生成的字段并返回包装类/// </summary>/// <param name="creator">生成器</param>public static TempWrapper<T> WrapByCustom(Func<T> creator){lock (_key){try{TempWrapper<T> wrapper = new TempWrapper<T>() { _value = creator() };return wrapper;}catch (Exception e){throw new InvalidOperationException("Failed to wrap.", e);}}}/// <summary>/// 解包包装器并赋值给指定的字段/// </summary>/// <remarks>/// <para>提示:采用二进制序列化和反序列化解包</para>/// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>/// </remarks>public void UnWrapByBinary(ref T value){if (_isValueType) value = _value;else{lock (_key){using (MemoryStream ms = new MemoryStream()){IFormatter formatter = new BinaryFormatter();formatter.Serialize(ms, _value);ms.Seek(0, SeekOrigin.Begin);value = (T)formatter.Deserialize(ms);}}}}/// <summary>/// 解包包装器并赋值给指定的字段/// <para>提示:采用JSON序列化和反序列化解包</para>/// </summary>public void UnwrapByJson(ref T value){if (_isValueType) value = _value;else{lock (_key){string jsonStr = JsonConvert.SerializeObject(_value);value = JsonConvert.DeserializeObject<T>(jsonStr);}}}/// <summary>/// 释放包装器所包装的字段/// <para>提示:当所包装字段实现了IDisposable接口时该方法才有效</para>/// </summary>public void Dispose(){if (_isDisposed) return;DoDispose(true);GC.SuppressFinalize(this);}void DoDispose(bool disposing){if (_isDisposed) return;_isDisposed = true;if (disposing && _isDisposable && _value is IDisposable ds)ds.Dispose();}~TempWrapper(){DoDispose(false);}
}
v1.1
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;/// <summary>
/// 临时包装器
/// </summary>
/// <typeparam name="T">字段类型</typeparam>
/// <remarks>
/// 该类主要用于创造某个字段的副本作为该字段的临时缓存,避免直接修改源字段。
/// </remarks>
public class TempWrapper<T> : IDisposable
{/// <summary>/// 是否为值类型/// <para>提示:若为true则表示包装字段为值类型,否则为引用类型</para>/// </summary>public static bool isValueType => _isValueType;/// <summary>/// 缓存字段/// <para>提示:对于值类型而言,该属性涉及拷贝</para>/// </summary>public T value{get{if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");return _value;}set{if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");_value = value;}}/// <summary>/// 获取引用/// <para>提示:对于值类型而言,该属性直接返回引用从而避免拷贝</para>/// </summary>public ref T refrence{get{if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");return ref _value;}}static readonly bool _isValueType = typeof(T).IsValueType;static readonly bool _isDisposable = typeof(IDisposable).IsAssignableFrom(typeof(T));static readonly object _key = new object();T _value;bool _isDisposed;TempWrapper() { }/// <summary>/// 包装指定字段并返回包装类/// </summary>/// <param name="value">待包装字段的引用</param>/// <remarks>/// <para>提示:采用二进制序列化和反序列化生成字段副本</para>/// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>/// </remarks>public static TempWrapper<T> WrapByBinary(ref T value){lock (_key){try{TempWrapper<T> wrapper = new TempWrapper<T>();if (_isValueType) wrapper._value = value;else{using (MemoryStream ms = new MemoryStream()){IFormatter formatter = new BinaryFormatter();formatter.Serialize(ms, value);ms.Seek(0, SeekOrigin.Begin);wrapper._value = (T)formatter.Deserialize(ms);}}return wrapper;}catch (Exception e){throw new InvalidOperationException("Failed to wrap.", e);}}}/// <summary>/// 包装指定字段并返回包装类/// <para>提示:采用JSON序列化和反序列化生成字段副本</para>/// </summary>/// <param name="value">待包装字段的引用</param>public static TempWrapper<T> WrapByJson(ref T value){lock (_key){try{TempWrapper<T> wrapper = new TempWrapper<T>();if (_isValueType) wrapper._value = value;else{string jsonStr = JsonConvert.SerializeObject(value);wrapper._value = JsonConvert.DeserializeObject<T>(jsonStr);}return wrapper;}catch (Exception e){throw new InvalidOperationException("Failed to wrap.", e);}}}/// <summary>/// 包装生成器所生成的字段并返回包装类/// </summary>/// <param name="creator">生成器</param>public static TempWrapper<T> WrapByCustom(Func<T> creator){lock (_key){try{TempWrapper<T> wrapper = new TempWrapper<T>() { _value = creator() };return wrapper;}catch (Exception e){throw new InvalidOperationException("Failed to wrap.", e);}}}/// <summary>/// 解包包装器并赋值给指定的字段/// </summary>/// <remarks>/// <para>提示:采用二进制序列化和反序列化解包</para>/// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>/// </remarks>public void UnWrapByBinary(ref T value){if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");if (_isValueType) value = _value;else{using (MemoryStream ms = new MemoryStream()){IFormatter formatter = new BinaryFormatter();formatter.Serialize(ms, _value);ms.Seek(0, SeekOrigin.Begin);value = (T)formatter.Deserialize(ms);}}}/// <summary>/// 解包包装器并赋值给指定的字段/// <para>提示:采用JSON序列化和反序列化解包</para>/// </summary>public void UnwrapByJson(ref T value){if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");if (_isValueType) value = _value;else{string jsonStr = JsonConvert.SerializeObject(_value);value = JsonConvert.DeserializeObject<T>(jsonStr);}}/// <summary>/// 释放包装器所包装的字段/// <para>提示:当所包装字段实现了IDisposable接口时该方法才有效</para>/// </summary>public void Dispose(){if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");DoDispose(true);GC.SuppressFinalize(this);}void DoDispose(bool disposing){if (_isDisposed) return;_isDisposed = true;if (disposing && _isDisposable && _value is IDisposable ds)ds.Dispose();}~TempWrapper(){DoDispose(false);}
}
测试
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;// TempWrapper测试脚本
public class TempWrapperTest : MonoBehaviour
{[SerializeField] int[] intArray;[SerializeField] string[] strArray;[SerializeField] StructA[] structaArray;[SerializeField] ClassA classA;[SerializeField] StructA structA;[SerializeField] Transform tf;[Serializable]struct StructA{public string key;public int value;public override string ToString(){return $"(key:{key},value:{value})";}}[Serializable]class ClassA{public string key;public StructA structA;public override string ToString(){StringBuilder builder = new StringBuilder("[key:");builder.Append(key).Append(",").Append($"StructA:{structA}");builder.Append("]");return builder.ToString();}}TempWrapper<int[]> intArrayWrapper;TempWrapper<string[]> strArrayWrapper;TempWrapper<StructA[]> structaArrayWrapper;TempWrapper<ClassA> classaWrapper;TempWrapper<StructA> structaWrapper;TempWrapper<Transform> tfWrapper;void Awake(){if (Application.isPlaying){intArrayWrapper = TempWrapper<int[]>.WrapByBinary(ref intArray);strArrayWrapper = TempWrapper<string[]>.WrapByJson(ref strArray);structaArrayWrapper = TempWrapper<StructA[]>.WrapByBinary(ref structaArray);classaWrapper = TempWrapper<ClassA>.WrapByBinary(ref classA);structaWrapper = TempWrapper<StructA>.WrapByBinary(ref structA);tfWrapper = TempWrapper<Transform>.WrapByCustom(() => Instantiate(tf));Instantiate(transform);}}void OnDestroy(){intArrayWrapper.Dispose();strArrayWrapper.Dispose();structaArrayWrapper.Dispose();classaWrapper.Dispose();structaWrapper.Dispose();tfWrapper.Dispose();}void Update(){if (Input.GetKeyDown(KeyCode.Q)){PrintWrapper();PrintHashCode();PrintWrapperHashCode();}if (Input.GetKeyDown(KeyCode.W)){WriteWrapper();UnWrap();}}void PrintWrapper(){intArrayWrapper.value.LogC("IntArrayWrapper:");strArrayWrapper.value.LogC("StrArrayWrapper:");structaArrayWrapper.value.LogC(s => $"[key:{s.key},value:{s.value}]", "StructaArrayWrapper:");LogUtility.Log("ClassAWrapper:" + classaWrapper.value);LogUtility.Log("StructAWrapper:" + structaWrapper.value);LogUtility.Log("TfWrapper:" + tfWrapper.value.position);}void PrintHashCode(){LogUtility.Log("IntArray:" + intArray.GetHashCode());LogUtility.Log("StrArray:" + strArray.GetHashCode());LogUtility.Log("StructaArray:" + structaArray.GetHashCode());LogUtility.Log("ClassA:" + classA.GetHashCode());LogUtility.Log("StructA:" + structA.GetHashCode());LogUtility.Log("Tf:" + tf.GetHashCode());}void PrintWrapperHashCode(){LogUtility.Log("IntArrayWrapper:" + intArrayWrapper.value.GetHashCode());LogUtility.Log("StrArrayWrapper:" + strArrayWrapper.value.GetHashCode());LogUtility.Log("StructaArrayWrapper:" + structaArrayWrapper.value.GetHashCode());LogUtility.Log("ClassAWrapper:" + classaWrapper.value.GetHashCode());LogUtility.Log("StructAWrapper:" + structaWrapper.value.GetHashCode());LogUtility.Log("TfWrapper:" + tfWrapper.value.GetHashCode());}void WriteWrapper(){List<int> ints = new List<int>(intArrayWrapper.value) { 99, 100 };intArrayWrapper.value = ints.ToArray();List<string> strs = new List<string>(strArrayWrapper.value) { "D", "E" };strArrayWrapper.value = strs.ToArray();List<StructA> strcutAs = new List<StructA>(structaArrayWrapper.value){new StructA { key = "D", value = 99 },new StructA { key = "E", value = 100 }};structaArrayWrapper.value = strcutAs.ToArray();structaWrapper.refrence.key = "E";structaWrapper.refrence.value = 1000;classaWrapper.value.key = "DE";classaWrapper.value.structA.key = "D";classaWrapper.value.structA.value = 999;tfWrapper.value.position = Vector3.zero;}void UnWrap(){intArrayWrapper.UnWrapByBinary(ref intArray);strArrayWrapper.UnwrapByJson(ref strArray);structaArrayWrapper.UnWrapByBinary(ref structaArray);structaWrapper.UnWrapByBinary(ref structA);classaWrapper.UnWrapByBinary(ref classA);}
}
#endif
v1.0
| 用例ID | 用例名称 | 前者测试 | 预期结果 | 是否通过 |
|---|---|---|---|---|
| 1 | 简单值类型数组 | 无 | 可缓存 | 通过 |
| 2 | 不可变引用类型数组 | 无 | 可缓存 | 通过 |
| 3 | 复合值类型数组 | 无 | 可缓存 | 通过 |
| 4 | 自定义引用类型 | 无 | 可缓存 | 通过 |
| 5 | 自定义值类型 | 无 | 可缓存 | 通过 |
| 6 | Unity对象 | 无 | 可缓存 | 通过 |
v1.1
| 用例ID | 用例名称 | 前者测试 | 预期结果 | 是否通过 |
|---|---|---|---|---|
| 1 | 简单值类型数组 | 无 | 可缓存 | 通过 |
| 2 | 不可变引用类型数组 | 无 | 可缓存 | 通过 |
| 3 | 复合值类型数组 | 无 | 可缓存 | 通过 |
| 4 | 自定义引用类型 | 无 | 可缓存 | 通过 |
| 5 | 自定义值类型 | 无 | 可缓存 | 通过 |
| 6 | Unity对象 | 无 | 可缓存 | 通过 |
分析
字段临时缓存包装器有三种包装字段的方式,分别是WrapByBinary、WrapByJson和WrapByCustom,三种方式各有优缺点,择优而用。WrapByBinary采用二进制序列化和反序列化生成字段副本,该方法仅可用于被 Serializable 标记的字段类型。WrapByJson采用JSON序列化和反序列化生成字段副本,它虽然比前者包装范围更广,但是不可避免可能会依赖第三方用于JSON序列化和反序列化的库。WrapByCustom则是对前两种方式的补充,当前两种方式都不适用时,则可以自定义包装方式,例如对于Unity对象来说,需要通过Instantiate方法创建对象副本,这个时候就只能用自定义的方法进行包装。
返回的包装器提供了一些属性和方法,可用于判断是否为值类型、缓存的字段和缓存字段的引用(值类型),提供了针对WrapByBinary和WrapByJson包装方法的解包方法,还提供了显式释放包装器的方法。对于包装值类型时,我们可以通过获取缓存字段的引用来避免拷贝,解包方法用于将临时缓存的数据重新写入被包装字段中。通过显式释放包装器可以保证那些使用了非托管资源的类型(实现了IDisposable接口)进行资源的释放工作,从而避免内存泄漏等问题。
但是该包装器存在一些不可避免的限制,若所包装字段越复杂,其性能损耗越高,这是不可避免的。而对于复杂类型,建议自定义一个数据结构作为临时缓存的包装类型。
注意:不要使用包装器去包装其声明所在的类,特别是对于自定义包装逻辑要避免无限递归,否则会导致栈溢出或内存泄漏问题。示例代码如下:
// 该方法将导致栈溢出 public class A {TempWrapper<A> wrapper;public A(){wrapper = TempWrapper<A>.WrapByCustom(() => new A());} }// 该方法将导致内存泄漏 public class B:Monobehaviour {TempWrapper<Transform> wrapper;void Awake(){// Instantiate方法去克隆当前组件对象的Transform组件就会导致无限递归wrapper = TempWrapper<Transform>.WrapByCustom(() => Instantiate(transform));} }
版本改进
| 版本号 | 改进内容 |
|---|---|
| v1.1 | 1.实例方法不使用线程锁,仅对静态方法使用线程锁; 2.手动执行Dispose方法释放包装器后,所有对公开实例成员的访问都将触发异常; 3.删除IsDisposed属性; |
| ...... | ...... |
系列文章
......
如果这篇文章对你有帮助,请给作者点个赞吧!
相关文章:
字段临时缓存包装器
前言 在实际开发中,我们有时候存在一种需求,例如对于某个字段,我们希望在某个明确的保存节点前对字段的修改都仅作为缓存保留,最终是否应用这些修改取决于某些条件,比如玩家对游戏设置的修改可能需要玩家明确确认应用修…...
Python(三)——列表
文章目录 创建列表访问下标遍历列表元素新增元素查找元素删除元素连接列表切片操作 创建列表 创建列表主要有两种方式 [ ]表示一个空的列表 a [] print(type(a)) # <class list> print(a) # []通过list()的方式来创建一个空列表 a list() print(type(a)) # …...
MySQL--三大范式(超详解)
目录 一、前言二、三大范式2.1概念2.2第一范式(1NF)2.3第二范式(2NF)2.3第三范式(3NF) 一、前言 欢迎大家来到权权的博客~欢迎大家对我的博客进行指导,有什么不对的地方,我会及时改进…...
追梦无Bug的软件世界
追梦无Bug的软件世界:测试人员的视角与探索 我有一个梦想,今天我们共同承载着一个愿景:创造一个没有Bug的软件世界。 我梦想有一天,用户将享受到完全无Bug的软件体验,用户不再因为软件中的Bug而感到困扰和沮丧。 我梦…...
在C#中使用Redis实现高效消息队列
使用Redis实现C#中的消息队列 Redis是一种开源的内存数据结构存储系统,因其高性能和灵活性被广泛用于缓存、数据库和消息队列等场景。本文将详细介绍如何在C#中使用Redis实现一个简单的消息队列,涵盖环境准备、代码实现和使用示例。 1. 环境准备 1.1 安装Redis 首先,确保…...
微服务JMeter解析部署使用全流程
目录 1、介绍 2、下载 3、运行 4、设置简体中文版 5、开始测试 1、添加线程组 2、添加监听器 3、添加请求 先.测试userController里的查询方法 6、查看结果 1、查看结果树 2、汇总报告 3、聚合报告 7、JMeter报错 1、介绍 Apache JMeter 是 Apache 组织基于 Java…...
Python 从入门到实战32(数据库MySQL)
我们的目标是:通过这一套资料学习下来,通过熟练掌握python基础,然后结合经典实例、实践相结合,使我们完全掌握python,并做到独立完成项目开发的能力。 上篇文章我们讨论了数据库编程接口操作的相关知识。今天我们将学习…...
hrnet训练的pt模型结合目标检测进行关键点识别的更准确前向推理
本篇在将图像输入hrnet识别之前先进行目标检测来确定识别的位置,让识别更加精准。 本段代码设置了一个区域框BOX,让人走入区域内才开始检测,适用于考核等场景,也可以直接去掉BOX也是一样的效果。若画面背景中有多个行人࿰…...
Leetcode 3306. Count of Substrings Containing Every Vowel and K Consonants II
Leetcode 3306. Count of Substrings Containing Every Vowel and K Consonants II 1. 解题思路2. 代码实现 题目链接:3306. Count of Substrings Containing Every Vowel and K Consonants II 1. 解题思路 这一题的话思路上就是一个滑动窗口,考察没一…...
算法笔记(五)——分治
文章目录 算法笔记(五)——分治快排颜色分类排序数组数组中的第K个最大元素库存管理 III 归并排序数组交易逆序对的总数计算右侧小于当前元素的个数翻转对 算法笔记(五)——分治 分治算法字面上的解释是“分而治之”,就…...
多级侧边菜单(递归)
需要编写两个文件 aside-menu.vue 和 menu-item.vue menu-item.vue <script setup> defineOptions({name: MenuItem}) defineProps({menuList: Array}) </script><template><template v-for"menu of menuList"><!-- 如果当前有子菜单&a…...
JavaScript break与continue语句
break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行。 break break语句用于跳出代码块或循环 for(i0;i<100;i){if(i5){break;}console.log(i);} continue continue语句用于应即终止本轮循环,返回循环结构的头部,开始下一轮循环。…...
算法【从递归入手一维动态规划】
动态规划:用空间代替重复计算,包含一整套原理和技巧的总和。后面会有非常多的文章介绍动态规划。 有些递归在展开计算时,总是重复调用同一个子问题的解,这种重复调用的递归变成动态规划很有收益。如果每次展开都是不同的解&#…...
Linux中的进程间通信之共享内存
共享内存 共享内存示意图 共享内存数据结构 struct shmid_ds {struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kerne…...
第18周 3-过滤器
过滤器(Filter)概念总结 什么是过滤器 过滤器(Filter)是Java Web应用中用于统一拦截和处理请求的组件,类似于现实生活中的空气净化器或安检。它通过对请求进行前置处理,确保请求符合特定要求。 过滤器的…...
Linux之进程概念
作者主页: 作者主页 本篇博客专栏:Linux专栏 创作时间 :2024年9月28日 基本概念: 进程说白了其实就是一个程序的执行实例,正在执行的程序。 在内核层面来说,就是一个担当分配资源(CPU时间…...
小程序-使用npm包
目录 Vant Weapp 安装 Vant 组件库 使用 Vant 组件 定制全局主题样式 API Promise化 1. 基于回调函数的异步 API 的缺点 2. 什么是 API Promise 化 3. 实现 API Promise 化 4.调用 Promise 化之后的异步 API 小程序对 npm 的支持与限制 目前,小程序中已经…...
【springboot】整合沙箱支付
目录 1. 配置沙箱应用环境2. 配置springboot项目1. 引入依赖2. 配置文件注册下载ngrok 3. 创建支付宝支付服务类4. 支付界面模板5. 控制类实现支付6. 测试 1. 配置沙箱应用环境 使用支付宝账号登录到开放平台控制台。 使用支付宝登录后,看到以下页面,下…...
技术速递|Python in Visual Studio Code 2024年9月发布
排版:Alan Wang 我们很高兴地宣布将于 2024 年 9 月发布适用于 Visual Studio Code 的 Python 和 Jupyter 扩展! 此版本包括以下公告: Django 单元测试支持使用 Pylance 从 inlay 提示转到定义 如果您有兴趣,可以在我们的 Pyth…...
数据结构-3.5.队列的顺序实现
一.队列的顺序实现,初始化操作以及判断队列是否为空: 1.图解: 2.代码: #include<stdio.h> #define MaxSize 10 //定义一个队列最多存储的元素个数 typedef struct {int data[MaxSize]; //用静态数组存放队列元素int f…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
