字段临时缓存包装器

前言

在实际开发中,我们有时候存在一种需求,例如对于某个字段,我们希望在某个明确的保存节点前对字段的修改都仅作为缓存保留,最终是否应用这些修改取决于某些条件,比如玩家对游戏设置的修改可能需要玩家明确确认应用修改后才会保存下来,在此之前玩家在游戏界面上的所有修改都是临时的。


本文基于这个需求探索出了一种解决方案“字段临时缓存包装器”,通过创建字段或用于存储字段临时数据的数据结构的副本来实现临时缓存,虽然我们同样可以采用直接声明一个副本字段的方式来达到同样的目的,但是这可能会增加冗余代码,且不利于代码的维护,通过包装器来封装临时缓存的通用逻辑,与具体业务逻辑隔离。

代码

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自定义值类型可缓存通过
6Unity对象可缓存通过
v1.1
用例ID用例名称前者测试预期结果是否通过
1简单值类型数组可缓存通过
2不可变引用类型数组可缓存通过
3复合值类型数组可缓存通过
4自定义引用类型可缓存通过
5自定义值类型可缓存通过
6Unity对象可缓存通过

分析

字段临时缓存包装器有三种包装字段的方式,分别是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属性;

............

系列文章

......

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

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

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

相关文章

代码随想录 102. 沉没孤岛

102. 沉没孤岛 #include<bits/stdc.h> using namespace std;void dfs(vector<vector<int>>& mp, vector<vector<int>>& visit, int y, int x){if (mp[y][x] 0 || visit[y][x] 1) return;if (mp[y][x] 1 && visit[y][x] 0) …

go语言protoc的详细用法与例子

一. 原来的项目结构 二. 选择源proto文件及其目录&目的proto文件及其目录 在E:\code\go_test\simple_demo\api 文件夹下&#xff0c;递归创建\snapshot\helloworld\v1\ad.pb.go E:\code\go_test\simple_demo> protoc --go_outpathssource_relative:./api .\snapshot\h…

[OS] EXPORT_SYMBOL()

在 Linux 内核中&#xff0c;EXPORT_SYMBOL() 用于将模块中的函数或变量导出&#xff0c;使得其他内核模块能够使用这些导出的符号。这对于模块之间共享功能或数据非常有用。给出的代码示例展示了如何使用 EXPORT_SYMBOL() 将变量和函数导出供其他模块使用。 /* ... */ int GL…

Dolma:包含三万亿Token的语言模型预训练研究开放语料库

前言 原论文&#xff1a;Dolma: an Open Corpus of Three Trillion Tokens for Language Model Pretraining Research 摘要 关于训练当前最佳性能语言模型的预训练语料库的信息很少被讨论——商业模型很少详细说明它们的数据&#xff0c;即使是开源模型也往往在没有训练数据…

Ubuntu开机进入紧急模式处理

文章目录 Ubuntu开机进入紧急模式处理一、问题描述二、解决办法参考 Ubuntu开机进入紧急模式处理 一、问题描述 Ubuntu开机不能够正常启动&#xff0c;自动进入紧急模式&#xff08;You are in emergency mode&#xff09;。具体如下所示&#xff1a; 二、解决办法 按CtrlD进…

基于开源大型lmm模型生成标签对InternVL2-1B等轻量lmm模型进行微调

基于开源大型lmm模型生成标签对InternVL2-1B等轻量lmm模型进行微调,提升InternVL2-1B等轻量lmm模型的能力。本实验在window下,基于3060 12g显卡进行实验。基于qwen2-vl 7b模型生成标签(电脑显存大的话可以考虑qwen2-vl 72b模型),然后对InternVL2-1B进行Lora微调。以voc201…

Perl 子程序(函数)

Perl 子程序&#xff08;函数&#xff09; Perl 是一种高级、解释型、动态编程语言&#xff0c;广泛用于CGI脚本、系统管理、网络编程、 finance, bioinformatics, 以及其他领域。在Perl中&#xff0c;子程序&#xff08;也称为函数&#xff09;是组织代码和重用代码块的重要方…

《机器学习》周志华-CH10(降维与度量学习)

10.1k近邻学习 k k k近邻(k-Nearest Neighbor,简称kNN)&#xff0c;监督学习。 工作机制&#xff1a;给定测试样本&#xff0c;基于某种距离度量找出训练集中与其最靠近的 k k k个训练样本&#xff0c;基于这些”邻居“预测。 { 分类任务&#xff1a;选择”投票法“。 k 个样本…

MySQL之复合查询与内外连接

目录 一、多表查询 二、自连接 三、子查询 四、合并查询 五、表的内连接和外连接 1、内连接 2、外连接 前面我们讲解的mysql表的查询都是对一张表进行查询&#xff0c;即数据的查询都是在某一时刻对一个表进行操作的。而在实际开发中&#xff0c;我们往往还需要对多个表…

如何使用MATLAB代码生成器生成ADRC跟踪微分器(TD) PLC源代码(SCL)

ADRC线性跟踪微分器TD详细测试 ADRC线性跟踪微分器TD详细测试(Simulink 算法框图+CODESYS ST+博途SCL完整源代码)-CSDN博客文章浏览阅读383次。ADRC线性跟踪微分器(ST+SCL语言)_adrc算法在博途编程中scl语言-CSDN博客文章浏览阅读784次。本文介绍了ADRC线性跟踪微分器的算法和…

关于启动flask应用,其他主机无法访问flask应用的错误记录

目录 前言解决办法 前言 因为一些原因&#xff0c;接触到了web应用框架flask。在一台主机上启动flask app&#xff0c;尝试过将app.run中的host参数设置为127.0.0.1;0.0.0.0。甚至是服务器主机的ip&#xff0c;可结果是只有服务器本机才能访问服务&#xff0c;其他不管是外网&…

Bolt.new:终极自动化编程工具

兄弟们&#xff0c;终极写代码工具来了—— Bolt.new&#xff01;全方位的编程支持&#xff1a; StackBlitz 推出了 Bolt․new&#xff0c;这是一款结合了 AI 与 WebContainers 技术的强大开发平台&#xff0c;允许用户快速搭建并开发各种类型的全栈应用。 它的主要特点是无需…

Anaconda的安装与环境设置

文章目录 一、Anaconda介绍二、Anaconda环境搭建1. 下载Anaconda(1)官网下载(2)清华大学镜像 2. 安装Anaconda3.配置环境变量4.检验conda是否安装成功5.更改镜像源6.若菜单栏没有conda prompt 三、虚拟环境1.创建、查看、删除虚拟环境2.激活、退出虚拟环境 四、CUDA、Pytorch、…

JS 入门

文章目录 JS 入门一、JS 概述1、JS 特点2、JS 组成3、JS 初体验4、HTML引入JS 二、JS 基础语法1、变量声明2、基本数据类型3、引用数据类型1&#xff09;数组2&#xff09;对象3&#xff09;函数4&#xff09;null 4、运算符5、条件判断6、循环语句 三、JS 函数0、JS 函数特点1…

上传文件失败,请检查阿里云配置信息:[The specified bucket is not valid.

-- 十一假期结束 -- 去年今日此门中&#xff0c;人面挑花相应红。 -- 人面不知何处去&#xff0c;桃花依旧笑春风。

Pikachu-unsafe upfileupload-getimagesize

什么是getimagesize()&#xff1f; getimagesize()是PHP中用于获取图像的大小和格式的函数。它可以返回一个包含图像的宽度、高度、类型和MIME类型的数组。 由于返回的这个类型可以被伪造&#xff0c;如果用这个函数来获取图片类型&#xff0c;从而判断是否时图片的话&#xff…

虚拟机 VMware 安装 macOS

macOS 界面 MAC OS IOS下载&#xff1a; amacOS Monterey by Techrechard.comwmacOS Monterey by Techrechard.com 下载&#xff1a;Unlocker-v2.0.1-x64 Mac OS X 虚拟机中更改屏幕分辨率 终端输入命令&#xff1a; sudo defaults write /Library/Preferences/com.apple.w…

vim编辑器安装,并修改配置使其默认显示行数

centOS默认是未安装vim编辑器的&#xff0c;而vim编辑器相比vi编辑器更易用一些&#xff0c;如需使用vim编辑器&#xff0c;需要进行安装。 1.需要先配置本地yum源&#xff0c;参见如下链接&#xff1a; 点击查看如何配置本地yum源 2.安装vim编辑器&#xff0c;并修改配置。…

昇思学习打卡营学习记录:Pix2Pix实现图像转换

按照提示&#xff0c;运行实训代码 进入实训平台&#xff1a;https://xihe.mindspore.cn/projects 选择“jupyter 在线编辑器” 启动“Ascend开发环境” &#xff1a;Ascend开发环境需要申请&#xff0c;大家可以申请试试看 启动开发环境后&#xff0c;在左边的文件夹中&am…

kafka和zookeeper单机部署

安装kafka需要jdk和zookeeper环境&#xff0c;因此先部署单机zk的测试环境。 zookeeper离线安装 下载地址&#xff1a; zookeeper下载地址&#xff1a;Index of /dist/zookeeper 这里下载安装 zookeeper-3.4.6.tar.gz 版本&#xff0c;测试环境单机部署 上传服务器后解压缩 …