当前位置: 首页 > news >正文

Unity Jobsystem ECS

简介

随着ECS的加入,Unity基本上改变了软件开发方面的大部分方法。ECS的加入预示着OOP方法的结束。随着实体组件系统ECS的到来,我们在Unity开发中曾使用的大量实践方法都必须进行改变以适应ECS,也许不少人需要些时间适应ECS的使用,但是ECS会对游戏性能的提升产生很大作用。

面向对象编程是一个很好的编程模式,OOP非常容易掌握和易于理解,尤其适合初学者。OOP的最大优点是它的可访问性,开发者可以在几乎没有任何相关知识的情况下创建类并维护编写的代码。然而OOP方法带有严重的缺点,它会给总体性能带来不好的影响,因为OOP方法很难避免出现重复的代码,造成一定程度上的性能开销,而且OOP虽然简单,但是却很依赖引用。

面向对象编程是基于特定对象的概念实现的,对象的种类由类的实例定义,它们通过互相交互来构建程序。对象可以包含属性和方法等形式的数据。

ECS实体组件系统的方法和OOP不同,它的数据和行为/方法是明确分离的,这样能大大提高内存使用效率,从而提高性能。

现在对于Unity而言,ECS还处于早期阶段,有很大的发展空间,但是开发者已经可以开始使用ECS。本文将讲解ECS的常见方法,Hybrid ECS和Pure ECS,并且介绍ECS的实现方法、语法以及如何开始使用ECS。

ECS方法

对于ECS,我们会更多谈论实体(Entity),而不是游戏对象。或许你会觉得实体和游戏对象没有太大区别,因为你可以将实体视为组件的容器(Container)。但是,如果你深入研究的话,会发现二者区别很大,实体其实只是特定组件的句柄。

OOP和ECS所提到的“组件”是否相同?不,这二者并不一样。在ECS出现前,我们通常将附加到游戏对象上的MonoBehaviour视为组件。MonoBehaviour包含数据和行为,它的数据来自所有变量,行为来自用于定义和调用游戏对象的函数。ECS是不同的,因为实体和组件都没有任何行为逻辑,它们只包含数据。

所有逻辑都保存在Managers/Systems中,它会获取一组实体,然后根据分组实体所包含的数据来执行所请求的行为。这意味着现在,不是所有实体都会处理自己的行为,而是所有实体行为都会在同一位置进行处理。

要完全理解为什么ECS方法比旧OOP方法的速度更快,你需要了解内存管理的相关知识。在OOP方法中,数据不会被组织起来,而是会分散再整个内存中,这是因为使用了自动内存管理功能。

自动内存管理功能介绍

像C#这种语言,内存的分配和释放过程是通过垃圾收集器自动完成的。该过程开始后,Mono平台会从操作系统请求特定容量的内存,并使用该部分内存来生成代码可使用的堆内存空间。该堆空间随着代码需要使用更多内存而逐渐增大。如果不再需要之前声明的内存,内存会释放回操作系统,堆的大小也会随之减小,这便是垃圾回收器的工作方式。

对于之后的内存分配,如果大小合适的话,垃圾回收器会使用之前用于保存数据并在释放后产生的剩余“间隙”。

因此,将数据从内存移动到缓存需要消耗较多性能,因为必须先找到引用才可以进行移动。

对于内存管理而言,ECS更加优化,因为ECS的数据会根据类型进行保存,而不是根据数据的分配时间。试想一下,一个商店根据商品的上架顺序放置商品,另一个商店根据商品的分类放置商品,你认为哪个商店的做法更好?

ECS性能更高效的另一个原因是,由于数据已明确地分离出来,ECS只会缓存相关数据。这是什么意思呢?

当使用OOP时,无论何时访问游戏对象,即使只需要一个特定属性,都必须缓存该对象的所有属性,该做法会对性能产生很大影响,因为缓存这些类型的话,会导致原生系统和托管系统之间的交互,而这样做就会产生垃圾,导致垃圾回收的发生。

Hybrid ECS

目前想纯粹使用ECS来编写一个完整的游戏还不太现实,因为部分Unity功能还未支持ECS,但是这个原因不会阻止我们使用ECS。

Hybrid ECS允许我们将ECS逻辑结合到现有的项目中,利用ECS的优点,而且不会影响还未支持ECS的功能的使用。

Hybrid ECS会和helper类一同工作,例如:Game Object Entity,它会将游戏对象转换为实体,并将附带的MonoBehaviour转换为组件。

我们编写的C#脚本派生自MonoBehaviour,它只包含数据但没有行为,然后我们将这些MonoBehaviour附加到带有Game Object Entity的游戏对象上。

我们的行为会放到Manager/System中,该类必须派生自ComponentSystem。我们不需要将该类附加到场景中的游戏对象,因为Unity会检测到该类并自动执行它。

在System类中,我们将通过定义它附带的组件来将Entity定义为struct。我们不会使用Update函数,而是使用OnUpdate函数来获取实体,并在实体中进行迭代并执行行为。

ECS Solar System (Hybrid)

我们已经了解到实体组件系统是什么,现在来研究一下实际案例。

如前文所所述,学习使用ECS对一些人而言很难,因为必须改变自己编写程序的方法。希望下面部分内容能够帮助你轻松理解该过程。

我们将使用Hybrid ECS编写一个小型宇宙,因为Hybrid ECS非常容易掌握,希望看完本案例后,你不会对ECS感到恐惧,而是更自信地尝试使用ECS。

项目准备

本文中使用的版本是Unity 2018.2.0.2,首先从资源包管理器获取Entities资源包。打开Window >> PackageManager,找到Entities进行安装。

安装完成后,Console中会出现报错内容,但这是正常现象。我们还要将脚本运行时版本从.Net 3.5改为.Net 4.x,方法是打开Build Settings >> Player Settings,修改脚本运行时版本。

预制件

Hybrid ECS拥有能将MonoBehaviour转换为组件的helper类,我们来仔细研究一下。

首先我们为星球、行星、椭圆形和月球创建大量预制件。

从上图中可以看到,该预制件就像普通预制件一样,没有什么特别之处。预制件上带有Transform、Mesh Filter、Mesh Renderer和Mesh Collider。

该预制件上添加了Game Object Entity组件。该组件不是自定义编写的脚本,而是前面提到的helper类,它将把普通游戏对象转换为实体。

Planet Comonent组件,代码如下:

using UnityEngine;

public class PlanetComponent : MonoBehaviour

{

public float rotationSpeed;

public float orbitDuration;

public OrbitalEllipse orbit;

}

Components Job用来保存数据,所以在该函数中没有其它内容。下面列出了所有需要的组件,这些组件就不进行太多讲解。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class MoonComponent : MonoBehaviour {

public float movementSpeed;

public GameObject parentPlanet;

}

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class OrbitalElipseComponent : MonoBehaviour {

public float xExtent;

public float yExtent;

public GameObject parent;

}

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class StarComponent : MonoBehaviour {

[Range(0f, 100f)] public float twinkleFrequency;

}

这样组件和预制件就设置好了,是不是非常简单。

HybridECSSolarSystem

在我们通过创建组件来处理所有数据之前,如果仔细阅读前文,就知道现在我们需要处理行为。

我们将添加新C#脚本,命名为HybridECSSolarSystem,我们不会将该脚本附加到任何对象上,实际上它甚至不是MonoBehaviour,它派生自ComponentSystem。

Unity会在内部检测并执行该脚本,请记得添加using Unity.Entities声明。我们将在该类的开始定义Stars、Planets和Moons的struct,前面提到实体是组件分组的句柄,所以Stars类型的实体是附带StarComponent和MeshRenderer组件的所有实体。

或许你还注意到,我们没有Update()函数,而是使用了protected override void OnUpdate(),派生自ComponentSystem的每个类都需要拥有OnUpdate()函数,我们的实体行为将在该函数中执行。

仔细观察OnUpdate()函数中第一个foreach循环会发现,该循环获取了所有Stars类型的实体,然后循环处理每个实体。我们通过检查依赖starComponent.twinkleFrequency实体的Random.Range来获得随机性。

using UnityEngine;

using Unity.Entities;

public class HybridECSSolarSystem : ComponentSystem

{

struct Stars

{

public StarComponent starComponent;

public MeshRenderer renderer;

}

struct Planets

{

public PlanetComponent planetComponent;

public Transform transform;

}

struct Moons

{

public Transform transform;

public MoonComponent moonComponent;

}

protected override void OnUpdate()

{

foreach (var starEntity in GetEntities<Stars>())

{

int timeAsInt = (int)Time.time;

if(Random.Range(1f, 100f) < starEntity.starComponent.twinkleFrequency)

{

starEntity.renderer.enabled = timeAsInt % 2 == 0;

}

}

foreach (var planetEntity in GetEntities<Planets>())

{

planetEntity.transform.Rotate(Vector3.up * Time.deltaTime * planetEntity.planetComponent.rotationSpeed, Space.Self);

planetEntity.transform.position = planetEntity.planetComponent.orbit.Evaluate(Time.time / planetEntity.planetComponent.orbitDuration);

}

foreach (var moonEntity in GetEntities<Moons>())

{

Vector3 parentPos = moonEntity.moonComponent.parentPlanet.transform.position;

Vector3 desiredPos = (moonEntity.transform.position - parentPos).normalized * 5f + parentPos;

moonEntity.transform.position = Vector3.MoveTowards(moonEntity.transform.position, desiredPos, moonEntity.moonComponent.movementSpeed);

moonEntity.transform.RotateAround(moonEntity.moonComponent.parentPlanet.transform.position, Vector3.up, moonEntity.moonComponent.movementSpeed);

}

}

}

我们没有通过以前的方法使用Update函数,而是执行了System类中的Update,这就是在使用Hybrid ECS时的区别。

虽然我们已经完成将游戏对象转换为实体所需的操作,但我们还需要一个类来实例化银河系,现在进入下一部分。

HybridECSInstantiator

HybridECSInstantiator类的内容非常简单明了,不必进行过多赘述。我们在实例化整个太阳系的场景中,设置了一组变量用来创建游戏对象。

我们通过使用处理对象位置的onUnitSphere和UniverseRadius,将宇宙的大致结构视为球形。通过计算生成椭圆形,然后使用LineRenderer组件进行绘制,我们便得到了椭圆形轨道。

我们使用了HybridECSSolarSystem.cs类中的Ellipse.Evaluate()函数实现了行星的移动。

对于每个对象,不管是Star还是Planet,我们只是实例化这些对象,设置它们的组件并放置到合适的位置。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class HybridECSInstatiator : MonoBehaviour

{

[Header("General Settings:")]

[SerializeField] float universeRadius;

[Header("Sun:")]

[SerializeField] GameObject sunPrefab;

[SerializeField] Vector3 sunPosition;

[Header("Moon:")]

[SerializeField] GameObject moonPrefab;

[SerializeField] float minMoonMovementSpeed;

[SerializeField] float maxMoonMovementSpeed;

[Header("Stars:")]

[SerializeField] GameObject starPrefab;

[SerializeField] float minStarsize;

[SerializeField] float maxStarsize;

[SerializeField] int starsAmount;

[SerializeField] [Range(0, 100)] float minTwinkleFrequency;

[SerializeField] [Range(0, 100)] float maxTwinkleFrequency;

[Header("Orbital Elipses:")]

[SerializeField] int elipseSegments;

[SerializeField] float elipseWidth;

[SerializeField] GameObject orbitalElipsePrefab;

[Header("Planets:")]

[SerializeField] List<Planet> planets = new List<Planet>();

static HybridECSInstatiator instance;

public static HybridECSInstatiator Instance { get { return instance; } }

GameObject sun;

void Awake()

{

instance = this;

PlaceSun();

PlaceStars();

PlacePlanets();

}

#region Sun

void PlaceSun()

{

sun = Instantiate(sunPrefab, sunPosition, Quaternion.identity);

GameObject sunParent = new GameObject();

sunParent.name = "Sun";

sun.transform.parent = sunParent.transform;

}

#endregion

#region Stars

void PlaceStars()

{

GameObject starParent = new GameObject();

starParent.name = "Stars";

for (int i = 0; i < starsAmount; i++)

{

GameObject currentStar = Instantiate(starPrefab);

currentStar.transform.parent = starParent.transform;

currentStar.GetComponent<StarComponent>().twinkleFrequency = Random.Range(minTwinkleFrequency, maxTwinkleFrequency);

float randomStarScale = Random.Range(minStarsize, maxStarsize);

currentStar.transform.localScale = new Vector3(randomStarScale, randomStarScale, randomStarScale);

currentStar.transform.position = Random.onUnitSphere * universeRadius;

currentStar.SetActive(true);

}

}

#endregion

#region OrbitalElipses

void DrawOrbitalElipse(LineRenderer line, OrbitalEllipse ellipse)

{

Vector3[] drawPoints = new Vector3[elipseSegments + 1];

for (int i = 0; i < elipseSegments; i++)

{

drawPoints[i] = ellipse.Evaluate(i / (elipseSegments - 1f));

}

drawPoints[elipseSegments] = drawPoints[0];

line.useWorldSpace = false;

line.positionCount = elipseSegments + 1;

line.startWidth = elipseWidth;

line.SetPositions(drawPoints);

}

#endregion

#region Planets

void PlacePlanets()

{

GameObject planetParent = new GameObject();

planetParent.name = "Planets";

for (int i = 0; i < planets.Count; i++)

{

GameObject currentPlanet = Instantiate(planets[i].planetPrefab);

currentPlanet.transform.parent = planetParent.transform;

currentPlanet.GetComponent<PlanetComponent>().rotationSpeed = planets[i].rotationSpeed;

currentPlanet.GetComponent<PlanetComponent>().orbitDuration = planets[i].orbitDuration;

currentPlanet.GetComponent<PlanetComponent>().orbit = planets[i].orbit;

GameObject currentElipse = Instantiate(orbitalElipsePrefab, sunPosition, Quaternion.identity);

currentElipse.transform.parent = sun.transform;

DrawOrbitalElipse(currentElipse.GetComponent<LineRenderer>(), planets[i].orbit);

if(planets[i].hasMoon)

{

GenerateMoon(currentPlanet);

}

}

}

#endregion

#region Moons

void GenerateMoon(GameObject planet)

{

GameObject moonParent = new GameObject();

moonParent.name = "Moons";

GameObject currentMoon = Instantiate(moonPrefab);

currentMoon.transform.parent = moonParent.transform;

currentMoon.GetComponent<MoonComponent>().movementSpeed = Random.Range(minMoonMovementSpeed, maxMoonMovementSpeed);

currentMoon.GetComponent<MoonComponent>().parentPlanet = planet;

}

#endregion

}

[System.Serializable]

public class OrbitalEllipse

{

public float xExtent;

public float yExtent;

public float tilt;

public Vector3 Evaluate(float _t)

{

Vector3 up = new Vector3(0, Mathf.Cos(tilt * Mathf.Deg2Rad), -Mathf.Sin(tilt * Mathf.Deg2Rad));

float angle = Mathf.Deg2Rad * 360f * _t;

float x = Mathf.Sin(angle) * xExtent;

float y = Mathf.Cos(angle) * yExtent;

return up * y + Vector3.right * x;

}

}

[System.Serializable]

public class Planet

{

public GameObject planetPrefab;

public OrbitalEllipse orbit;

public bool hasMoon;

[Header("Movement Settings:")]

public float rotationSpeed;

public float orbitDuration;

}

这样一个小型的宇宙便实现了!

相关文章:

Unity Jobsystem ECS

简介随着ECS的加入&#xff0c;Unity基本上改变了软件开发方面的大部分方法。ECS的加入预示着OOP方法的结束。随着实体组件系统ECS的到来&#xff0c;我们在Unity开发中曾使用的大量实践方法都必须进行改变以适应ECS&#xff0c;也许不少人需要些时间适应ECS的使用&#xff0c;…...

Java中创建线程有哪几种方式

1.继承Thread类 总结&#xff1a;通过继承 Thread 类&#xff0c;重写 run() 方法&#xff0c;而不是 start() 方法 Thread 类底层实现 Runnable 接口类只能单继承 接口可以多继承2.实现Runnable接口 总结&#xff1a;通过实现 Runnable 接口,实现 run() 方法&#xff0c;依然…...

C++【string类用法详细介绍string类模拟实现解析】

文章目录string 类用法介绍及模拟实现一、string介绍二、string类常用接口1. string类对象的常见构造接口2.string类对象的常见容量接口3.string类对象的常见修改接口4. string类对象的常见访问及遍历接口5.string其他接口1.不常用查找接口2.字符替换3.字符串拼接4.字符串排序5…...

常见的开发模型和测试模型

软件的生命周期软件开发阶段的生命周期需求分析->计划->设计->编码->测试->运维软件测试阶段的生命周期需求分期->测试计划->测试设计与开发->执行测试->测试评估开发模型瀑布模型可以看到,这个模型和我们上面的软件开发生命周期很相似采用的是线性…...

印度和印度尼西亚有什么关系吗?

印度和印度尼西亚&#xff0c;这两个国家很多人都比较熟悉。因为两国都是人口大国&#xff0c;而且经济总量也比较高&#xff0c;在全球还是有很大影响的。不过很多人刚看到这两个国家的时候&#xff0c;都会觉得这两个国家肯定有什么关系&#xff0c;要不然国名也不会这么像。…...

单调栈(C/C++)

目录 1. 单调栈的定义 2. 单调栈的常见用途 3. 案例分析 3.1 暴力解法 3.2 单调栈 4. 单调栈总结 1. 单调栈的定义 单调栈顾名思义&#xff0c;就是栈内的元素是单调的。根据栈内元素的单调性的不同&#xff0c;可以分为&#xff1a; 单调递增栈&#xff1a;栈内元素是单…...

算法设计与智能计算 || 专题一: 算法基础

专题一: 算法基础 文章目录专题一: 算法基础1. 算法的定义及特点1.1 算法的基本特征1.2 算法的基本要素1.3 算法的评定2 算法常见执行方法2.1 判断语句2.2 循环语句2.3 综合运用3. 计算复杂度4. 代码的重用5. 类函数的定义与使用5.1 定义类5.2 调用类函数1. 算法的定义及特点 …...

用javascript分类刷leetcode13.单调栈(图文视频讲解)

239. 滑动窗口最大值 (hard) 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入&#xff1a;nums [1,…...

英语基础语法学习(B站英语电力公司)

1. 句子结构 五大基本句型&#xff1a; 主谓主谓宾主谓宾宾主谓宾宾补主系表 谓语&#xff1a; 一般来说&#xff0c;谓语是指主语发出的动作。&#xff08;动词&#xff09;但是很多句子是没有动作的&#xff0c;但是还是必须要有谓语。&#xff08;此时需要be动词&#x…...

【计算机网络】网络层IP协议

文章目录一、认识IP协议二、IP协议头部格式三、IP地址划分1. IP地址分类2. 子网划分四、IP地址数量危机1. IP地址的数量限制2. NAT技术五、私网IP和公网IP六、路由1. 认识路由2. 路由表生成算法一、认识IP协议 IP协议是Internet Protocol&#xff08;互联网协议&#xff09;的…...

Eclipse快捷键大全

编辑类快捷键Ctrl1: 快速修复(最经典的快捷键, 可以解决很多问题, 比如import类、try catch包围等)CtrlShiftF: 格式化当前代码CtrlShiftM: 添加类的import导入CtrlShiftO: 组织类的导入(既有CtrlShiftM的作用,又可以去除没用的导入, 一般用这个导入包)CtrlY: 重做(与CtrlZ相反…...

JavaScript 高级2 :构造函数和原型 d331702016e84f54b3594ae05e0eeac

JavaScript 高级2 &#xff1a;构造函数和原型 Date: January 16, 2023 Text: 构造函数和原型、继承、ES5中的新增方法 目标 能够使用构造函数创建对象 能够说出原型的作用 能够说出访问对象成员的规则 能够使用 ES5新增的一些方法 构造函数和原型 概述 在典型的 OOP 的…...

maven-war-plugin插件 overlays maven-war-plugin翻译

说明 翻译maven-war-plugin插件的部分内容 官方地址为&#xff1a;https://maven.apache.org/plugins/maven-war-plugin/index.html Overview 概述 Introduction 介绍 Apache Maven WAR Plugin apache maven war 插件 The WAR Plugin is responsible for collecting all artifa…...

【数据结构】初识二叉树(二叉树的入门知识)

初识二叉树一、树概念及结构1、树的概念2、树的相关概念3、树的表示4、树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09;二、二叉树概念及结构1、概念2、特殊的二叉树3、二叉树的性质4、二叉树的存储结构三、结语一、树概念及结构 1、树的概念 树是一种非线…...

RV1126笔记三十二:基于 FastDeploy 在 RV1126 上的部署示例(RV1126 上部署 YOLOv5 检测模型测试)

若该文为原创文章,转载请注明原文出处。 FastDeploy是一款全场景、易用灵活、极致高效的AI推理部署工具, 支持云边端部署。提供超过 🔥160+ Text,Vision, Speech和跨模态模型📦开箱即用的部署体验,并实现🔚端到端的推理性能优化。包括 物体检测、字符识别(OCR)、…...

JVM垃圾回收——G1垃圾收集器

目录 一、什么是G1垃圾收集器 二、G1垃圾收集器的内存划分 三、G1垃圾收集器的收集过程 四、G1收集器的优缺点 五、G1收集器的JVM参数配置 一、什么是G1垃圾收集器 Garbage First(简称G1)收集器是垃圾收集器技术发展史上里程碑式的成果&#xff0c;它摒弃了传统垃圾收集器的…...

C语言深度剖析:关键字

C语言深度剖析:关键字C语言深度剖析:关键字前言定义与声明&#xff08;补充内容&#xff09;最宏大的关键字-auto最快的关键字-register关键字static被冤枉的关键字-sizeof整型在内存中的存储原码、反码、补码大小端补充理解变量内容的存储和取出为什么都是补码整型取值范围关于…...

聊一聊过度设计!

文章目录什么是过度设计&#xff1f;过度设计的坏处如何避免过度设计充分理解问题本身保持简单小步快跑征求其他人的意见总结新手程序员在做设计时&#xff0c;因为缺乏经验&#xff0c;很容易写出欠设计的代码&#xff0c;但有一些经验的程序员&#xff0c;尤其是在刚学习过设…...

程序员在小公司(没有大牛,人少)怎么成长?

大多数小公司都是创业公司&#xff0c;所以它们有着非常独特的“创业心态”。所谓创业心态通常表现为关注快速增长&#xff0c;竭尽所能让公司盈利&#xff0c;或者达成其他一些迫切目标。 在这样一家公司工作的软件开发人员&#xff0c;你极有可能要身兼多职&#xff0c;不能…...

【Fastdfs实战】在本地如何将文件上传到Linux虚拟机

作者&#xff1a;狮子也疯狂 专栏&#xff1a;《Fastdfs连续剧》 坚持做好每一步&#xff0c;幸运之神自然会驾凌在你的身上 目录一. &#x1f981; 前言二. &#x1f981; 上传原理Ⅰ. &#x1f407; 原理图解Ⅱ. &#x1f407; 传输原理三. &#x1f981; 实战演示Ⅰ. &…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...

macOS 终端智能代理检测

&#x1f9e0; 终端智能代理检测&#xff1a;自动判断是否需要设置代理访问 GitHub 在开发中&#xff0c;使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新&#xff0c;例如&#xff1a; fatal: unable to access https://github.com/ohmyzsh/oh…...

【Redis】Redis从入门到实战:全面指南

Redis从入门到实战:全面指南 一、Redis简介 Redis(Remote Dictionary Server)是一个开源的、基于内存的键值存储系统,它可以用作数据库、缓存和消息代理。由Salvatore Sanfilippo于2009年开发,因其高性能、丰富的数据结构和广泛的语言支持而广受欢迎。 Redis核心特点:…...

Web APIS Day01

1.声明变量const优先 那为什么一开始前面就不能用const呢&#xff0c;接下来看几个例子&#xff1a; 下面这张为什么可以用const呢&#xff1f;因为复杂数据的引用地址没变&#xff0c;数组还是数组&#xff0c;只是添加了个元素&#xff0c;本质没变&#xff0c;所以可以用con…...

JS的传统写法 vs 简写形式

一、条件判断与逻辑操作 三元运算符简化条件判断 // 传统写法 let result; if (someCondition) {result yes; } else {result no; }// 简写方式 const result someCondition ? yes : no;短路求值 // 传统写法 if (condition) {doSomething(); }// 简写方式 condition &…...