基于ScriptableObject设计游戏数据表

前言

本篇文章是针对之前对于ScriptableObject概念讲解的实际应用之一,在游戏开发中,我们可以使用该类来设计编辑器时的可读写数据表或者运行时的只读数据表。本文将针对运行时的只读数据表的应用进行探索,并且结合自定义的本地持久化存储方式使得基于ScriptableObject开发的数据表能够在运行时进行读写。

代码

代码目录结构
  • Table
    • Base
    • Editor
    • Interface
    • Unit

Table则为本模块的根目录,存储各个游戏数据表的脚本,Base目录存储数据表和游戏表基类,Editor目录存储数据表的编辑器脚本,Interface目录存储数据表和数据单元接口,Unit目录存储数据单元。

Base目录 

BaseTable.cs

using System;
using UnityEngine;/// <summary>
/// 基础表
/// </summary>
public abstract class BaseTable : ScriptableObject
{/// <summary>/// 表类型/// </summary>public abstract Type mType { get; }
}

GameTable.cs

using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using UnityEngine;/// <summary>
/// 游戏表
/// </summary>
/// <typeparam name="T0">表类型</typeparam>
/// <typeparam name="T1">表单元类型</typeparam>
public class GameTable<T0, T1> : BaseTable, ITableHandler<T0, T1>
where T0 : GameTable<T0, T1>
where T1 : ITableUnit
{[Tooltip("是否自动控制加载和保存")] public bool isAutoControl = true;[HideInInspector, SerializeField] protected T1[] units;#if UNITY_EDITOR
#pragma warning disable CS0414[HideInInspector, SerializeField] bool isAutoSave = true;
#pragma warning restore CS0414
#endifpublic sealed override Type mType => typeof(T0);public ReadOnlyCollection<T1> mUnits => Array.AsReadOnly(wrapper.value);public int mCount => wrapper.value == null ? 0 : wrapper.value.Length;public event Action<T1> mModifiedCallback;protected string jsonPath;protected TempWrapper<T1[]> wrapper;/// <summary>/// 保存到本地/// </summary>public virtual void SaveLocally(){if (Application.isEditor) return;wrapper.UnWrapByBinary(ref units);string jsonStr = JsonUtility.ToJson(this);if (!string.IsNullOrEmpty(jsonStr)){string dirPath = Path.GetDirectoryName(jsonPath);if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);if (!File.Exists(jsonPath)) File.Create(jsonPath).Dispose();using (FileStream fs = new FileStream(jsonPath, FileMode.OpenOrCreate, FileAccess.Write)){byte[] bytes = Encoding.UTF8.GetBytes(jsonStr);fs.Write(bytes, 0, bytes.Length);fs.Flush();fs.Close();}}}/// <summary>/// 从本地加载/// </summary>public virtual void LoadFromLoacl(){if (Application.isEditor) return;if (File.Exists(jsonPath)){using (TextReader tr = new StreamReader(jsonPath, Encoding.UTF8)){string jsonStr = tr.ReadToEnd();if (!string.IsNullOrEmpty(jsonStr)){try{JsonUtility.FromJsonOverwrite(jsonStr, this);int len = units.Length;wrapper.value = new T1[len];units.CopyTo(wrapper.value, 0);InvokeModifiedEvents();}catch (Exception e){LogUtility.Log(e.Message, LogType.Error);}}tr.Close();}}else{string dirPath = Path.GetDirectoryName(jsonPath);if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);if (!File.Exists(jsonPath)) File.Create(jsonPath).Dispose();}}public virtual void ShareUnitsWith(T1[] array){int len = wrapper.value.Length;if (array == null || array.Length != len)array = new T1[len];for (int i = 0; i < len; i++){array[i] = wrapper.value[i];}}public virtual void SetDefault(){T0 table = Resources.Load<T0>(GamePathUtility.GetTableResourcesPath<T0>());if (table != null){int len = table.units.Length;wrapper.value = new T1[len];table.units.CopyTo(wrapper.value, 0);InvokeModifiedEvents();}}public virtual T1 Get(Func<T1, bool> logic){if (logic == null) return default;int len = wrapper.value.Length;for (int i = 0; i < len; i++){ref T1 unit = ref wrapper.value[i];if (logic(unit)) return unit;}return default;}public virtual T1 Get(int index){int len = wrapper.value.Length;if (index < 0 || index >= len) return default;return wrapper.value[index];}public virtual void Set(Func<T1, T1> logic){if (logic == null) return;int len = wrapper.value.Length;for (int i = 0; i < len; i++){wrapper.value[i] = logic(wrapper.value[i]);}InvokeModifiedEvents();}void InvokeModifiedEvents(){if (mModifiedCallback != null){int len = wrapper.value.Length;for (int i = 0; i < len; i++){mModifiedCallback.Invoke(wrapper.value[i]);}}}void Awake(){jsonPath = Path.Combine(Application.dataPath, $"Json/{mType.Name}.json");}void OnEnable(){if (units == null) units = Array.Empty<T1>();if (wrapper == null) wrapper = TempWrapper<T1[]>.WrapByBinary(ref units);if (isAutoControl) LoadFromLoacl();}void OnDisable(){if (isAutoControl) SaveLocally();if (wrapper != null){wrapper.Dispose();wrapper = null;}}
}
Interface目录 

ITableHandler.cs

using System;
using System.Collections.ObjectModel;// 表处理接口
public interface ITableHandler<TTable, TUnit> where TTable : BaseTable where TUnit : ITableUnit
{/// <summary>/// 表单元合集的只读视图/// </summary>ReadOnlyCollection<TUnit> mUnits { get; }/// <summary>/// 表单元合集中元素个数/// </summary>int mCount { get; }/// <summary>/// 表单元合集更改回调/// </summary>event Action<TUnit> mModifiedCallback;/// <summary>/// 分享表单元合集给指定的数组变量/// </summary>/// <param name="array">指定的数组变量</param>void ShareUnitsWith(TUnit[] array);/// <summary>/// 设置为默认值/// </summary>void SetDefault();/// <summary>/// 获取表单元/// </summary>/// <param name="logic">获取逻辑</param>TUnit Get(Func<TUnit, bool> logic);/// <summary>/// 获取表单元/// </summary>/// <param name="index">索引</param>TUnit Get(int index);/// <summary>/// 修改表单元/// </summary>/// <param name="logic">修改逻辑</param>void Set(Func<TUnit, TUnit> logic);
}

ITableUnit.cs

// 表单元接口
public interface ITableUnit { }
Editor目录 

GameTableEditor.cs

using UnityEditor;
using UnityEngine;// 游戏表编辑器
public class GameTableEditor : Editor
{protected SerializedProperty units, isAutoSave;const string tip = "Should be saved after modification. Everything will be saved when we leave the inspector unless you don't check 'Is Auto Save'. In runtime, everything will be loaded from local in 'OnEnable' and saved to local in 'OnDisable' unless you don't check 'Is Auto Control'.";protected void Init(){units = serializedObject.FindProperty("units");isAutoSave = serializedObject.FindProperty("isAutoSave");}protected void SaveGUI(){if (GUILayout.Button("Save")) Save();isAutoSave.boolValue = EditorGUILayout.Toggle(isAutoSave.displayName, isAutoSave.boolValue);}protected void TipGUI(){EditorGUILayout.HelpBox(tip, MessageType.Info);}protected virtual void Save() { }void OnDisable() { if (isAutoSave.boolValue) Save(); }
}
示例(鼠标样式表)

CursorStyleUIUnit.cs

using System;
using UnityEngine;
using UnityEngine.UI;// 鼠标样式UI单元
[Serializable]
public class CursorStyleUIUnit
{[Tooltip("鼠标样式类型")] public CursorStyleType styleType;[Tooltip("Dropdown组件")] public Dropdown dropdown;[Tooltip("当前选项的Image组件")] public Image showImage;[Tooltip("Dropdown组件选项模板下自定义的Image组件")] public Image itemShowImage;
}

CursorStyleUnit.cs

using System;
using UnityEngine;// 鼠标样式单元
[Serializable]
public struct CursorStyleUnit : ITableUnit
{[Tooltip("鼠标样式的属性名称")] public string key;[Tooltip("鼠标样式的属性值")] public string value;public CursorStyleUnit(string key, string value){this.key = key;this.value = value;}
}

CursorStyleTable.cs

using UnityEngine;// 鼠标样式单元存储表
[CreateAssetMenu(fileName = "Assets/Resources/Tables/CursorStyleTable", menuName = "Custom/Create CursorStyle Table", order = 1)]
public sealed class CursorStyleTable : GameTable<CursorStyleTable, CursorStyleUnit>
{[HideInInspector, SerializeField] CursorShape defaultShape;[HideInInspector, SerializeField] CursorColor defaultColor;[HideInInspector, SerializeField] int defaultSize;/// <summary>/// 默认鼠标形状/// </summary>public CursorShape mDefaultShape => defaultShape;/// <summary>/// 默认鼠标颜色/// </summary>public CursorColor mDefaultColor => defaultColor;/// <summary>/// 默认鼠标尺寸/// </summary>public int mDefaultSize => defaultSize;
}

CursorStyleTableEditor.cs

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;[CustomEditor(typeof(CursorStyleTable))]
public sealed class CursorStyleTableEditor : GameTableEditor
{SerializedProperty defaultShape, defaultColor, defaultSize;ReorderableList list;string[] styleTypes; // 样式类型合集Dictionary<int, Style> styles; // key表示该项在整个集合中的索引,value表示样式Style defaultShapeStyle, defaultColorStyle, defaultSizeStyle; // 样式默认值GUIContent defaultShapeContent, defaultColorContent, defaultSizeContent;string[] shapeDisplayNames, colorDisplayNames, sizeDisplayNames; // 样式默认值下拉菜单选项int _shapeIndex, _colorIndex, _sizeIndex; // 样式默认值所选菜单项索引bool isStylesDirty;int shapeIndex{get => _shapeIndex;set{if (_shapeIndex != value){_shapeIndex = value;UpdateDefaultStyles(Array.FindIndex(styleTypes, t => t == CursorStyleConstant.SHAPE));}}}int colorIndex{get => _colorIndex;set{if (_colorIndex != value){_colorIndex = value;UpdateDefaultStyles(Array.FindIndex(styleTypes, t => t == CursorStyleConstant.COLOR));}}}int sizeIndex{get => _sizeIndex;set{if (_sizeIndex != value){_sizeIndex = value;UpdateDefaultStyles(Array.FindIndex(styleTypes, t => t == CursorStyleConstant.SIZE));}}}// 记录每种样式类型和值struct Style{public int styleTypeIndex; // 样式类型索引public string value; // 样式值public Style(int styleTypeIndex, string value){this.styleTypeIndex = styleTypeIndex;this.value = value;}public bool CompareTo(ref Style other){return styleTypeIndex == other.styleTypeIndex && value == other.value;}}void OnEnable(){Init();defaultShape = serializedObject.FindProperty("defaultShape");defaultColor = serializedObject.FindProperty("defaultColor");defaultSize = serializedObject.FindProperty("defaultSize");list = new ReorderableList(serializedObject, units, false, false, true, true){drawElementCallback = DrawUnitCallback,onAddCallback = OnAddElement,onRemoveCallback = OnDelElement};styleTypes = new string[] { CursorStyleConstant.SHAPE, CursorStyleConstant.COLOR, CursorStyleConstant.SIZE };styles = new Dictionary<int, Style>();defaultShapeStyle.styleTypeIndex = Array.FindIndex(styleTypes, t => t == CursorStyleConstant.SHAPE);defaultShapeStyle.value = ((CursorShape)defaultShape.intValue).ToString();defaultColorStyle.styleTypeIndex = Array.FindIndex(styleTypes, t => t == CursorStyleConstant.COLOR);defaultColorStyle.value = ((CursorColor)defaultColor.intValue).ToString();defaultSizeStyle.styleTypeIndex = Array.FindIndex(styleTypes, t => t == CursorStyleConstant.SIZE);defaultSizeStyle.value = defaultSize.intValue.ToString();int len = units.arraySize;SerializedProperty element;for (int i = 0; i < len; i++){element = units.GetArrayElementAtIndex(i);int styleTypeIndex = Array.IndexOf(styleTypes, element.FindPropertyRelative("key").stringValue);AddOrSetElement(i, new Style(styleTypeIndex, element.FindPropertyRelative("value").stringValue));}defaultShapeContent = new GUIContent(defaultShape.displayName, defaultShape.tooltip);defaultColorContent = new GUIContent(defaultColor.displayName, defaultColor.tooltip);defaultSizeContent = new GUIContent(defaultSize.displayName, defaultSize.tooltip);len = styleTypes.Length;for (int i = 0; i < len; i++){UpdateDefaultDisplayNames(i);}string str = defaultShapeStyle.value;_shapeIndex = Array.FindIndex(shapeDisplayNames, s => s == str);str = defaultColorStyle.value;_colorIndex = Array.FindIndex(colorDisplayNames, s => s == str);str = defaultSizeStyle.value;_sizeIndex = Array.FindIndex(sizeDisplayNames, s => s == str);}void DrawUnitCallback(Rect rect, int index, bool isActive, bool isFocused){if (index >= styles.Count) styles[index] = new Style();Style style = styles[index];rect.y += 2;style.styleTypeIndex = EditorGUI.Popup(new Rect(rect.x, rect.y, 80, EditorGUIUtility.singleLineHeight), style.styleTypeIndex, styleTypes);style.value = EditorGUI.TextField(new Rect(rect.x + 100, rect.y, rect.width - 100, EditorGUIUtility.singleLineHeight), style.value);UpdateStyle(ref style, index);}void OnAddElement(ReorderableList list){ReorderableList.defaultBehaviours.DoAddButton(list);AddOrSetElement(list.count - 1, new Style(0, string.Empty));}void OnDelElement(ReorderableList list){DelElement(list.index);ReorderableList.defaultBehaviours.DoRemoveButton(list);}void AddOrSetElement(int index, Style style){if (style.styleTypeIndex < 0 || style.styleTypeIndex >= styleTypes.Length|| string.IsNullOrEmpty(style.value) || index < 0 || index >= list.count) return;styles[index] = style;UpdateDefaultDisplayNames(style.styleTypeIndex);}void DelElement(int index){Style style = styles[index];styles.Remove(index);UpdateDefaultDisplayNames(style.styleTypeIndex);}void UpdateDefaultDisplayNames(params int[] styleTypeIndexes){if (styleTypeIndexes == null || styleTypeIndexes.Length == 0) return;int len = styleTypeIndexes.Length;var group = styles.GroupBy(kv => kv.Value.styleTypeIndex);string CONST_STR;IGrouping<int, KeyValuePair<int, Style>> temp;for (int i = 0; i < len; i++){int index = styleTypeIndexes[i];if (index < 0 || index >= styleTypes.Length) continue;CONST_STR = styleTypes[index];switch (CONST_STR){case CursorStyleConstant.SHAPE:temp = group.Where(g => g.Key == index).FirstOrDefault();if (temp != null) shapeDisplayNames = temp.Select(kv => kv.Value.value).ToArray();else shapeDisplayNames = Array.Empty<string>();break;case CursorStyleConstant.COLOR:temp = group.Where(g => g.Key == index).FirstOrDefault();if (temp != null) colorDisplayNames = temp.Select(kv => kv.Value.value).ToArray();else colorDisplayNames = Array.Empty<string>();break;case CursorStyleConstant.SIZE:temp = group.Where(g => g.Key == index).FirstOrDefault();if (temp != null) sizeDisplayNames = temp.Select(kv => kv.Value.value).ToArray();else sizeDisplayNames = Array.Empty<string>();break;}}}void UpdateDefaultStyles(params int[] styleTypeIndexes){if (styleTypeIndexes == null || styleTypeIndexes.Length == 0) return;int len = styleTypeIndexes.Length;string CONST_STR;for (int i = 0; i < len; i++){int index = styleTypeIndexes[i];if (index < 0 || index >= styleTypes.Length) continue;CONST_STR = styleTypes[index];switch (CONST_STR){case CursorStyleConstant.SHAPE:if (_shapeIndex < 0 || _shapeIndex >= shapeDisplayNames.Length)defaultShapeStyle.value = CursorShape.None.ToString();else defaultShapeStyle.value = shapeDisplayNames[_shapeIndex];break;case CursorStyleConstant.COLOR:if (_colorIndex < 0 || _colorIndex >= colorDisplayNames.Length)defaultColorStyle.value = CursorColor.None.ToString();else defaultColorStyle.value = colorDisplayNames[_colorIndex];break;case CursorStyleConstant.SIZE:if (_sizeIndex < 0 || _sizeIndex >= sizeDisplayNames.Length)defaultSizeStyle.value = "0";else defaultSizeStyle.value = sizeDisplayNames[_sizeIndex];break;}}}void UpdateStyle(ref Style style, int index){if (!styles[index].CompareTo(ref style)){styles[index] = style;isStylesDirty = true;}}public override void OnInspectorGUI(){serializedObject.Update();base.OnInspectorGUI();EditorGUILayout.LabelField("鼠标样式单元合集", EditorStyles.boldLabel);list.DoLayoutList();EditorGUILayout.LabelField("鼠标样式默认值", EditorStyles.boldLabel);if (isStylesDirty){isStylesDirty = false;for (int i = 0; i < styleTypes.Length; i++){UpdateDefaultDisplayNames(i);UpdateDefaultStyles(i);}}EditorGUI.BeginDisabledGroup(shapeDisplayNames.Length == 0);shapeIndex = EditorGUILayout.Popup(defaultShapeContent, shapeIndex, shapeDisplayNames);EditorGUI.EndDisabledGroup();EditorGUI.BeginDisabledGroup(colorDisplayNames.Length == 0);colorIndex = EditorGUILayout.Popup(defaultColorContent, colorIndex, colorDisplayNames);EditorGUI.EndDisabledGroup();EditorGUI.BeginDisabledGroup(sizeDisplayNames.Length == 0);sizeIndex = EditorGUILayout.Popup(defaultSizeContent, sizeIndex, sizeDisplayNames);EditorGUI.EndDisabledGroup();SaveGUI();TipGUI();serializedObject.ApplyModifiedProperties();}protected override void Save(){List<CursorStyleUnit> reserve = new List<CursorStyleUnit>();int len = styles.Count;for (int i = 0; i < len; i++){Style style = styles[i];if (!string.IsNullOrEmpty(style.value)){CursorStyleUnit v_unit = new CursorStyleUnit(styleTypes[style.styleTypeIndex], style.value);if (!reserve.Contains(v_unit)) reserve.Add(v_unit);}}units.ClearArray();styles.Clear();len = reserve.Count;CursorStyleUnit unit;SerializedProperty element;for (int i = 0; i < len; i++){units.InsertArrayElementAtIndex(i);element = units.GetArrayElementAtIndex(i);unit = reserve[i];element.FindPropertyRelative("key").stringValue = unit.key;element.FindPropertyRelative("value").stringValue = unit.value;styles[i] = new Style(Array.FindIndex(styleTypes, t => t == unit.key), unit.value);}for (int i = 0; i < styleTypes.Length; i++){UpdateDefaultDisplayNames(i);UpdateDefaultStyles(i);}if (Enum.TryParse(defaultShapeStyle.value, out CursorShape shape))defaultShape.intValue = (int)shape;if (Enum.TryParse(defaultColorStyle.value, out CursorColor color))defaultColor.intValue = (int)color;defaultSize.intValue = Convert.ToInt32(defaultSizeStyle.value);serializedObject.ApplyModifiedProperties();}
}

界面展示

分析

BaseTable作为所有表格的抽象基类并继承自ScriptableObject,用于后续扩展。ITableHandler声明表格的公开属性和行为。ITableUnit声明数据单元的公开属性和行为,作为暂留接口用于后续扩展,所有数据单元需要实现该接口。GameTable继承自BaseTable,并实现了ITableHandler接口,作为游戏数据表的基类,实现通用属性和方法,向具体游戏表类开放重写方法。GameTableEditor作为游戏数据表编辑器脚本的基类,实现通用逻辑。


示例中CursorStyleUIUnit作为鼠标样式的UI单元,负责定义UI界面上与表格数据相对应的UI组件。CursorStyleUnit作为鼠标样式的数据单元,负责定义每一项表格数据。CursorStyleTable则是定义鼠标样式表的具体逻辑。CursorStyleTableEditor用于定义鼠标样式表在编辑器Inspector面板中的GUI界面。


GameTable中isAutoControl字段用于启用运行时自动进行本地持久化管理的服务,在OnEnable方法中从本地持久化文件中加载内容,在OnDisable方法中将缓存内容保存至本地持久化文件中。isAutoSave字段用于启用编辑器时表格自动保存修改到资产文件的服务,若不勾选,每次在Inspector面板中进行修改后需要手动点击Save按钮进行保存,勾选后会自动保存。提供了指示表类型、表单元只读视图、表单元个数和修改回调等属性,以及本地持久化管理、表单元共享、表单元获取和设置以及重置为默认值等方法。


对表格进行设计后,我们可以使用表格管理器来统一管理所有表格,基于ScriptableObject的特性,我们可以为每个表格创建资产文件,通过加载资产文件即可获取表格实例。


TempWrapper称为字段临时缓存包装器,具体请看系列文章中与此相关的内容。

版本改进

......

系列文章

字段临时缓存包装器

如果这篇文章对你有帮助,请给作者点个赞吧!

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

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

相关文章

cheese安卓版纯本地离线文字识别插件

目的 cheese自动化平台是一款可以模拟鼠标和键盘操作的自动化工具。它可以帮助用户自动完成一些重复的、繁琐的任务&#xff0c;节省大量人工操作的时间。可以采用Vscode、IDEA编写&#xff0c;支持Java、Python、nodejs、GO、Rust、Lua。cheese也包含图色功能&#xff0c;识别…

SpringBoot——基础配置

但是还需要删除pom.xml中的标签——模板的文件也同样操作 banner的选项——关闭 控制台 日志 banner图片的位置——还会分辨颜色 在 Java 的日志框架&#xff08;如 Logback、Log4j2 等&#xff09;中&#xff0c;logging.level.root主要用于设置根日志记录器的日志级别…

css的盒模型

什么是盒模型&#xff1f; CSS盒模型&#xff08;CSS Box Model&#xff09;是CSS布局的基础&#xff0c;是CSS中用于设计和布局网页的一个核心概念。它定义了HTML元素的表现形式&#xff0c;包括元素的内部空间&#xff08;内容、内边距、边框&#xff09;和外部空间&#xf…

location指令

无前缀,必须以”/“开头 前缀""精准匹配。 前缀”^~“ 普通url匹配。 前缀”~“基于正则表达式的匹配&#xff0c; 区分大小写 前缀”~*“ 匹配优先级 locationlocation^~和无前缀/location ~或~* 1.无任何前缀 不加任何规则&#xff0c;默认大小写敏感&#x…

10.2 Linux_并发_进程相关函数

创建子进程 函数声明如下&#xff1a; pid_t fork(void); 返回值&#xff1a;失败返回-1&#xff0c;成功返回两次&#xff0c;子进程获得0(系统分配)&#xff0c;父进程获得子进程的pid 注意&#xff1a;fork创建子进程&#xff0c;实际上就是将父进程复制一遍作为子进程&…

【Linux操作系统】进程的创建与替换

目录 前言&#xff1a;一、进程创建1.fork();1.1 fork基本介绍1.2 fork的原理1.3 写时拷贝1.4 fork的使用场景1.5 fork调用失败的原因 2.clone() 二、进程替换(exec)1.替换原理2.替换函数3.函数解释4.函数理解 前言&#xff1a; 学习了Linux操作系统我们可以知道&#xff0c;进…

基于php摄影门户网站

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

【洛谷】AT_abc178_d [ABC178D] Redistribution 的题解

【洛谷】AT_abc178_d [ABC178D] Redistribution 的题解 洛谷传送门 AT传送门 题解 一个水水的动态规划&#xff0c;阿巴巴巴。 题目大概是这样&#xff1a; 给定一个正整数 S S S&#xff0c;问有多少个数满足以下条件&#xff1a; 序列中不能出现小于 3 3 3 的正整数。…

C语言_字符函数和字符串函数

1. 字符函数 1.1 字符分类函数 在C语言中&#xff0c;有一系列专门做字符分类的函数被包括在头文件<ctype.h>。 这些函数的区分范围如下&#xff1a; 函数如果他的参数符合下列条件就返回真iscntrl任何控制字符isspace空白字符&#xff1a;空格’ ‘、换页’\n‘、回…

Oracle架构之数据库备份和RAC介绍

文章目录 1 数据库备份1.1 数据库备份分类1.1.1 逻辑备份与物理备份1.1.2 完全备份/差异备份/增量备份 1.2 Oracle 逻辑备份1.2.1 EXP/IMP1.2.1.1 EXP导出1.2.1.2 EXP关键字说明1.2.1.3 导入1.2.1.4 IMP关键字说明 1.2.2 EXPDP/IMPDP1.2.2.1 数据泵介绍1.2.2.2 数据泵的使用 1.…

【STM32单片机_(HAL库)】4-3-2【定时器TIM】测量按键按下时间1——编程实现捕获功能

测量按键按下时长思路 测量按键按下时间实验目的 使用定时器 2 通道 2 来捕获按键 &#xff08;按键接PA0&#xff09;按下时间&#xff0c;并通过串口打印。 计一个数的时间&#xff1a;1us&#xff0c;PSC71&#xff0c;ARR65535 下降沿捕获、输入通道 2 映射在 TI2 上、不分…

TypeScript快速梳理

为何需要TypeScript ts存在静态类型检查&#xff1a;在代码运行前进行检查&#xff0c;发现代码的错误或不合理之处&#xff0c;减少运行时异常的出现的几率&#xff0c;此种检查叫静态类型检查&#xff0c; TypeScript的核心就是静态类型检查&#xff0c;简言之就是把运行时的…

汽车发动机控制存储芯片MR2A08A

MRAM在汽车发动机控制单元中的关键数据存储&#xff0c;MR2A08A容量4Mb的非易失性存储芯片&#xff0c;符合汽车AEC-Q100 1级合格选项&#xff0c;可以在遇到的非常高的温度环境下工作&#xff0c;足够快地实时读取或写入数据&#xff0c;是非易失性的。 MRAM速度快&#xff0…

华为-单臂路由

1、什么是单臂路由 单臂路由&#xff08;Single-Arm Routing&#xff09;是一种网络架构和配置技术&#xff0c;它允许路由器通过一个物理接口来管理多个虚拟局域网&#xff08;VLAN&#xff09;之间的通信。 这个物理接口被配置为Trunk模式&#xff0c;以便能够传输来自不同VL…

Redis缓存淘汰算法详解

文章目录 Redis缓存淘汰算法1. Redis缓存淘汰策略分类2. 会进行淘汰的7种策略2.1 基于过期时间的淘汰策略2.2 基于所有数据范围的淘汰策略 3. LRU与LFU算法详解4. 配置与调整5. 实际应用场景 LRU算法以及实现样例LFU算法实现1. 数据结构选择2. 访问频率更新3. 缓存淘汰4. 缓存插…

如何从huggingface下载

我尝试了一下若干步骤&#xff0c;莫名奇妙就成功了 命令行代理 如果有使用魔法上网&#xff0c;可以使用命令行代码&#xff0c;解决所有命令行连不上外网的问题&#xff1a; #配置http git config --global http.proxy 127.0.0.1:xxxx git config --global https.proxy 127…

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——15.红黑树

1.红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff0c;…

专访 Bitlayer 联合创始人 Charlie:探索比特币 Layer2 技术的未来

整理&#xff1a;Tia&#xff0c;Techub News 在加密货币行业经历了近 10 年的风雨历程后&#xff0c;Bitlayer 联合创始人 Charlie Hu 凭借其在以太坊、波卡等顶级项目中的深厚经验&#xff0c;重新聚焦比特币生态&#xff0c;他与 Bitlayer 的另外一位联合创始人 Kevin He 通…

k8s搭建双主的mysql8集群---无坑

《k8s搭建一主三从的mysql8集群---无坑-CSDN博客》通过搭建一主三从&#xff0c;我们能理解到主节点只有1个&#xff0c;那么承担增删改主要还是主节点&#xff0c;如果你在从节点上去操作增删改操作&#xff0c;数据不会同步到其他节点。本章我们将实现多主&#xff08;双主&a…

YOLO11关键改进与网络结构图

目录 前言&#xff1a;一、YOLO11的优势二、YOLO11网络结构图三、C3k2作用分析四、总结 前言&#xff1a; 对于一个科研人来说&#xff0c;发表论文水平的高低和你所掌握的信息差有着极大的关系&#xff0c;所以趁着YOLO11刚刚发布&#xff0c;趁热了解&#xff0c;先人一步对…