Unity的AssetBundle资源运行内存管理的再次深入思考

  大家好,我是阿赵。
  这篇文章我想写了很久,是关于Unity项目使用AssetBundle加载资源时的内存管理的。这篇文章不会分享代码,只是分享思路,思路不一定正确,欢迎讨论。
  对于Unity引擎的资源内存管理,我猜很多朋友都存在一定的疑惑。疑惑的点有非常多,包括资源怎样才能避免冗余,怎样才能不会在内存里面创建重复的资源内存,怎样才能在合适的时机把不需要的资源内存清理干净,什么时候能把AssetBundle本身清理掉。
  在这方面,我觉得Unity官方一直没有给用户一个很好的指导。或者说,使用Unity进行开发的游戏研发厂商们对引擎的使用,有点超出了Unity自己的预想,导致对于复杂的游戏类型,Unity本身已经不能用单一的策略去处理,只能提供自己的API,让厂商自己想办法做管理策略了。
  关于AssetBundle的使用基础,网上有很多文章介绍,我这里重点不是介绍基础用法,所以我会直接跳过介绍。然后我这里得出了很多测试结论,测试的过程我也不想逐个说明,因为基本上网上都有很多同类的测试。所以接下来,我会直接说出测试结果,然后提出我自己的解决方案。

一、资源对内存的占用分析

  通过AssetBundle加载的资源,在游戏运行时会产生多种内存,比较明显的有以下几种:

1、AssetBundle本身的内存占用

  如果是通过AssetBundle.LoadFromFile类方法加载,在没有LoadAsset之前,AssetBundle本身占用的内存很少,只是一个头文件的数据引用。
  如果是通过AssetBundle.LoadFromMemory类方法加载,那么文件的二进制是完全读取进入内存的,所以AssetBundle文件越大,占用的内存就越大。
  由于AssetBundle是互相依赖的,比如一个模型的预设AssetBundle,可能会依赖其他材质球、贴图、动画之类的AssetBundle,所以在加载主AssetBundle时,需要加载一堆依赖的AssetBundle。
  值得注意的是,对于同一个AssetBundle,运行时是不可能重复加载的,如果尝试重复加载同一个AssetBundle会有报错。所以同一个AssetBundle是不存在同时占用多份内存的情况。

2、Asset资源的内存占用

  这里的Asset资源,指的是通过AssetBundle.LoadAsset类方法,把AssetBundle里面的具体资源读取出来之后资源,比如预设Object、网格、材质球、贴图、动画、shader之类。
  Asset的资源占用才是占用内存的主要部分。当资源被LoadAsset出来之后,它会从二进制文件反序列化变成网格数据、贴图数据等。资源本身的复杂程度越高,占用的内存就越大。
  值得注意的是Asset的资源内存是可能重复的,而且出现重复的条件很多。比如有以下这些情况:
1.一张贴图同时被2个模型使用了,不同模型单独生成了AssetBundle,但这张作为共用依赖的贴图没有单独作为一个AssetBundle,而是同时包含在A、B两个模型的AssetBundle里面。那么运行时同时加载A、B两个模型,虽然看起来它们使用了同一张贴图,实际上内存里面是有2张重复的贴图的内存的。
2.AssetBundle.Unload(false)卸载之后,该AssetBundle内的资源还留在内存里,再重新加载同一个AssetBundle并LoadAsset资源,内存里面的资源就会有重复多份。

3、实例化对象的内存占用

  这里指的实例化,是指通过Object.Instantiate把预设的Object在场景里面实例化成GameObject。GameObject在场景里面肯定是有内存占用的,但它对自身的资源比如网格、贴图等,只是一个引用关系,并不会复制多一份资源本身。所以实例化GameObject占用的内存不会很大。

二、资源管理在理想中想达到的目的

  通过上面的分析,Asset内存占用是最大的,我们必须首先要避免重复,然后不用的时候要清除内存。
  实例化GameObject的内存不大,但也应该在删除GameObject的时候及时清除。
  AssetBundle的占用内存虽然同样不大,不过如果项目很庞大,加载的AssetBundle的数量很多,那么可能也会占用一定量的内存。我们肯定希望在一个AssetBundle不再使用的时候,把它Unload掉。
  所以最理想的状态就是,当我们加载一个资源,会顺便加载该资源依赖的所有AssetBundle,然后当我们确定某个资源不再被使用了之后,就把它们在场景里面删除,卸载Asset本身的内存,然后卸载资源的AssetBundle和依赖的其他AssetBundle。这样内存就干净了。
  这个看起来很清晰合理的目的,在Unity里面实现起来却并不那么容易,存在一些问题,下面会逐步分析。

三、清理资源内存的API

  为了方便理解管理策略,先来明确一下几个不同类型的资源内存的释放API:

1、AssetBundle资源卸载

AssetBundle资源的卸载,是通过AssetBundle.Unload方法实现,可以传入true或者false。
如果传入true会把该AssetBundle里面所有已经加载的资源都卸载掉,是最干净的清理方式,这样不管之前LoadAsset过什么资源,都会直接被清理掉了。如果该AssetBundle的资源还在使用中,那么这些资源都将会丢失。
如果传入false,那么单纯AssetBundle本身的引用内存会被清理掉,但LoadAsset的资源会保留。所以使用这种方式卸载,就算场景里面还有使用资源,也不会丢失。相对的,清理不那么干净,需要自己判断正在使用的资源什么时候释放。

2、Asset资源内存释放

  要卸载Asset的资源,有2个方法

1.Resources.UnloadAsset(Object assetToUnload)

  这个方法可以有针对性的释放某个指定的Asset对象资源。

2.Resources.UnloadUnusedAssets()

  这个方法是Unity自己管理的,Unity会遍历所有的资源,把所有没有被引用的资源卸载掉。这个方法消耗比较大,可能会导致游戏卡顿。

3、实例化资源内存卸载

  而GameObject本身的内存,删除之后会根据GC回收。通过Resources.UnloadUnusedAssets()也能把不再使用的内存回收。

四、需要解决的问题

  AssetBundle打包资源冗余的问题,之前我也写过好几篇文章去分析怎样做才是粒度最合理又没有冗余的。这里就不再重复,主要是说运行中加载卸载的问题。
  从最理想的情况下来看,如果我们能判断得很准确,知道哪些资源已经完全没有用到了,那么直接调用AssetBundle.Unload(true),资源肯定就可以清理得很干净,也不需要管Asset的内存,也不需要Resources.UnloadUnusedAssets。
  不过如果对判断资源使用情况的准确度不是那么的自信,一般是不敢直接AssetBundle.Unload(true)的,因为一个不好,判断错了,那么还在使用的资源就会突然间全部丢失了。所以一般的处理情况是AssetBundle.Unload(false)来清理AssetBundle,然后通过Resources.UnloadUnusedAssets来清理已经不再使用的Asset资源内存。
  我在网上看了一些文章的经验分享,有不少的建议是,对于直接加载读取的资源,可以读取出来Asset的Object之后,把Object缓存,然后立刻对该AssetBundle资源AssetBundle.Unload(false),而对作为依赖加载的AssetBundle做计数器处理。
  我个人认为,这个做法,对于主AssetBundle是prefab类的对象,是可以的。因为这些预制体都是需要实例化到场景里面,只需要在游戏里面做对象池,就可以很容易的知道资源是否还有在使用,也很容易做计数器。
  但如果是对于纯资源类的AssetBundle,比如图片,那就不太行了。比如我从AssetBundle里面加载一张图片,然后立刻Unload(false),那么这张图片的内存在什么时候清理呢?
  如果通过代码将图片存起来反复使用,那么很难判断图片现在还是否存活,举个例子,我把一张图片加载完之后赋予给了一个模型,然后模型本身被主动删除了或者切换场景被删除了,我们是不知道图片还在模型身上被删除的,除非每次模型被Destroy的时候,都遍历身上所有的Component,然后逐个资源类型去判断。那么被代码引用着的这张图片就永远不会回收。如果不用代码存起来,反复需要读取这张图片时,由于之前的AssetBundle被unload(false)了,如果重新加载AssetBundle,那么这张相同的图片,就会在内存里面生成多份不同的内存。
  通过上面的分析可以看出,实际上我们需要解决的问题只有一个,就是怎样很准确的知道我们的资源是否还在存活,是否还被引用,是否可以释放。我们需要一个很准确的依据。
  Unity本身肯定是知道的,因为在使用Resources.UnloadUnusedAssets的时候,Unity会把没用引用的资源都回收掉。但可惜的是,Unity没有提供直接的接口给我们查询。

五、弱引用计数

  C#有一个弱引用(WeakReference)的机制,可以判断一个对象是否存活。
  具体的用法是:

WeakReference weakRefObj = new WeakReference(obj);

然后通过weakRefObj.IsAlive可以判断该对象是否存活,然后通过weakRefObj.Target可以取到之前存进去的那个资源对象。
当一个对象在内存里面已经不存在的时候,这个弱引用对象的IsAlive会变成false,然后Target会变成null。
不过一般情况下,Asset本身是不会自然变成不存在于内存的,因为如果不是执行Resources.UnloadAsset或者Resources.UnloadUnusedAssets,资源并不会随着GameObject的删除而回收的。所以正常的情况下,想要弱引用能正确判断资源已经不存活,必须在删除GameObject时候,调用Resources.UnloadUnusedAssets。
所以如果正常的使用方法,应该是,用从AssetBundle里面LoadAsset得到的asset的对象,创建一个弱引用对象,然后如果需要使用该对象作为资源或者实例化对象的时候,先判断弱引用资源的IsAlive和Target,在保证IsAlive为true,并且Target不为null的情况下,可以取得Target去使用。如果IsAlive为false或者Target为null时,证明这个弱引用关联的资源已经没有任何人在使用了,就可以删除当前的弱引用对象。
不过这个弱引用对象,在Unity里面使用是有问题的,在Unity的自带文档的Overview of .NET in Unity页面里面,有这么一句说明:
在这里插入图片描述

Unity does not currently support the use of the C# WeakReference class
with UnityEngine.Objects. For this reason, you should not use a
WeakReference to reference a loaded asset. See Microsoft’s
WeakReference documentation for more information on the WeakReference
class.

  看了Unity的这个说明,对刚刚找到希望的我们来说,简直是晴天霹雳。不过究竟Unity对弱引用不支持到什么程度呢?
  我自己测试过,实际上在大部分情况下,弱引用对于Unity的Asset对象都是能正常判断的。但在有一种情况下,是会出问题的:
  如果我们从弱引用对象的Target实例化一个GameObject,然后删除这个GameObject并执行一次Resources.UnloadUnusedAssets,并且在很短的时间内再次访问弱引用对象的Target(只要访问,包括读取属性、打log、实例化),这时候,弱引用就大概率的会出错,具体出错的信息是这样的:
在这里插入图片描述

A scripted object (script unknown or not yet loaded) has a different
serialization layout when loading. (Read 32 bytes but expected 120
bytes) Did you #ifdef UNITY_EDITOR a section of your serialized
properties in any of your scripts?

  这个状态下,弱引用里面的Target并不为空,IsAlive也是true的,但Target被破坏了,再次取得这个Target就都是错的了,所有里面的资源都会丢失掉。
  上面提到了大概率会出问题,是在于调用Resources.UnloadUnusedAssets之后,多短时间内再次访问这个弱引用对象的Target,时间并不固定。有时候同一帧访问会出错,有时候又不会。
  这个现象,我做出了一些猜想,很有可能是因为Resources.UnloadUnusedAssets本身是异步处理的,会返回AsyncOperation,并不是同一帧就立刻清理完所有。在这个过程中,如果访问弱引用对象,它还没有完全判断到资源的清空情况,又一次去尝试访问这个资源本身,导致Resources.UnloadUnusedAssets清理到一半失败了。

六、加载和卸载的策略

  到了最后,来总结一下。
  关于怎样去加载和卸载AssetBundle的资源和Asset资源本身。策略可以有很多种,但核心的问题基本上就只有一个,就是什么时候才是真正可以释放资源内存。
  通过上面的分析知道,Unity并没有很明确的API接口让我们知道一个资源是否还在被引用着。弱引用虽然可以判断,但存在一定的问题,Unity本身是不建议我们这样做的。
  那么,接下来就可以分为2种可能性,一种是使用弱引用来判断,另外一种是不使用弱引用判断。

1、使用弱引用的情况

  使用弱引用最大的问题,是在Resources.UnloadUnusedAssets的过程中访问Target,会导致资源释放失败,并让Target永久的被破坏。其实这个时候我们只要重新去AssetBundle里面LoadAsset,那么就能解决Target被破坏的问题。但由于这种情况下弱引用对象的IsAlive是true,而Target也不为null,导致我们不知道Target被破坏了,而导致这个资源一直错下去。
  为了解决这个问题,我们只能严格的控制Resources.UnloadUnusedAssets的使用时机。切换场景也是会默认有UnloadUnusedAssets的调用的。
  于是,整体的加载和卸载过程就会变成这样:
加载:
1、建立一个AssetsMgr管理器,里面通过AssetBundleName和AssetName作为key,可以获取对应的资源。
2、逻辑层通过对象池来申请对象使用,假如对象池里面不存在可用对象,则去AssetsMgr里面获取Asset的Object。
3、AssetMgr里面保存的是WeakReference对象,如果该key没有被保存过,或者WeakReference对象的IsAlive不为true或者WeakReference对象的Target的对象为null,则需要重新去AssetBundleMgr管理器加载资源,并保存成为新的WeakReference。当AssetMgr里面已经有对应的WeakReference,那么将会返回作为Target的Object给对象池那边实例化使用。
4、AssetBundleMgr管理器里面保存着加载过的AssetBundle和它们所有的依赖关系。如果没有加载过对应的AssetBundle,将会在这里加载并返回。
卸载:
1、定时去遍历AssetMgr里面的所有弱引用对象,判断它们是否存活。如果已经不存活了,那么推进一个等待删除的列表,并等待一个短的时间再把它们真正清理。不立刻清理的原因是怕当前帧里面判断到不使用,然后立刻又有地方请求使用。
2、关于AssetBundle的卸载,按道理使用的Object已经存到弱引用管理里面,所以AssetBundle在加载完Asset的Object之后,就可以直接Unload(false)了。不过引用的资源的AssetBundle应该存起来,并通过计数器记录现在的使用情况。如果弱引用里面的主Asset被清理了,那么应该通知依赖AssetBundle计数器减一。当依赖AssetBundle的计数器为0时,则应该去卸载对应的AssetBundle。
特殊处理:
  由于弱引用存在一定的问题,所以要避免Resources.UnloadUnusedAssets或者切换场景之后,立刻去判断弱引用的存活。
  如果是可以频繁切换场景的游戏,比如回合制游戏,每次战斗都需要切换场景。那么可以用切换场景作为时机,每次切换场景时候等于是Resources.UnloadUnusedAssets了一次,然后等待Resources.UnloadUnusedAssets完全结束之后,才开始时机加载模型,并判断弱引用的使用情况。
  如果是不会切换地图场景的游戏,比如大地图SLG那种,建议是所有资源都做成异步加载,定时的执行Resources.UnloadUnusedAssets,然后在等待Resources.UnloadUnusedAssets执行完成之前,先把所有异步加载的请求和弱引用判断停掉,等待Resources.UnloadUnusedAssets结束,再继续执行弱引用加载请求和清理。

2、不使用弱引用的情况

  如果不敢使用弱引用计数的话,那么纯资源类型的引用就不好精确判断。

GameObject类资源

我们还是从对象池入手。
1.对于GameObject类的对象,我们做场景对象池,在每个对象需要实例化的时候,去AssetsMgr获取Object。而AssetsMgr里面同样是通过AssetBundleName和AssetName作为key 去保存资源,不过这时候就不是保存一个弱引用对象,而是保存一个计数器类对象,里面会保存Object本身,还有这个Object被实例化的次数。然后Object的GetInstanceID获得唯一Id,通过这个id也同样存一份对应计数器对象的字典。
2.当Object不存在的时候,去加载AssetBundle,并且加载依赖AssetBundle。然后LoadAsset出资源Object,主AssetBundle执行Unload(false),依赖的AssetBundle计数器加1。
3.当对象实例化的时候,在实例化对象身上挂一个脚本,里面记录着实例化这个GameObject的Object的InstanceID,脚本Awake的时候,抛出创建事件,InstanceID作为参数。AssesMgr里面监听这个事件,对InstanceID的引用加一。
4.当对象被删除的时候,身上的脚本的OnDestroy方法会抛出删除事件,InstanceID作为参数。AssetsMgr监听这个事件,并对InstanceID的引用减一。
5.定时去检查AssetsMgr里面的InstanceID计数器,如果有计数器数量为0的情况,就推入待删除队列,下一帧就把InstanceID对应的计数器删除,并且把该AssetBundle的依赖AssetBundle计数器减一,再检查依赖AssetBundle的计数器有等于0 的,执行Unload(false)。

纯资源

  对于纯资源类的Asset,比如图片之类的,如果不用弱引用,就只有有所取舍了。可以考虑一下这两种方案:
1、读取了AssetBundle里面的图片之后,AssetBundle本身立刻Unload(false),图片保存在代码里面复用。然后通过最后一次申请调用的时间去判断,大于一定时间之后,就清理掉。
这样做的好处是,AssetBundle本身的内存不会再占用了,然后如果很久没有人申请的资源,也只是在代码层面去掉引用,如果场景里面还有,也不会受到影响。
坏处是,如果一张图片在场景里面长久都没删除,代码引用被清理掉了。下次又申请了同一张图片使用,就要重新在AssetBundle加载一次,内存里面就会出现重复资源。
2、纯资源的asset对象不保存在代码引用,它们的AssetBundle也完全不卸载,这样每次需要使用该图片的时候,都从AssetBundle里面LoadAsset。然后定时执行Resources.UnloadUnusedAssets,让没有在使用的图片Asset内存得到释放。
  这样做的好处是,由于每次请求的资源都是从同一次加载的AssetBundle里面读取的,所以同一张图片的内存肯定不会有多份重复的。
  这样做的坏处是,引用AssetBundle占用内存由于没有释放的时机,会一直占用内存。不过实际上AssetBundle本身如果是通过LoadFromFile方法加载的话,AssetBundle不会占用太多的内存。
  至于LoadFromMemory类的方法加载,原则上是不建议这么做的,不过有些时候却迫不得已,比如要加密AssetBundle文件,或者混淆AssetBundle文件,就需要在文件的二进制里面做修改,然后使用时通过读取完整的二进制再进行解密。这种情况下,就肯定不能不释放AssetBundle文件了,不然内存的占用就非常大。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

调皮的String及多种玩法(下部)

👨‍💻作者简介:👨🏻‍🎓告别,今天 📔高质量专栏 :☕java趣味之旅 欢迎🙏点赞🗣️评论📥收藏💓关注 💖衷心的希…

0101插入排序-算法基础-算法导论第三版

文章目录 一 插入排序二 循环不变式与插入排序的正确性三 伪代码中的一些约定四 Java代码实现插入排序结语 一 插入排序 输入: n n n个数订单一个序列 ( a 1 , a 2 , ⋯ , a n ) (a_1,a_2,\cdots,a_n) (a1​,a2​,⋯,an​). **输出:**输入序列的一个排…

【how2j练习题】HTML部分综合练习

练习题 1 <html><h1>英雄联盟 &#xff08;电子竞技类游戏&#xff09;</h1> <p> <strong>《英雄联盟》</strong>&#xff08;简称lol&#xff09;是由美国<i>Riot Games</i>开发&#xff0c;中国大陆地区由腾讯游戏运营的网络…

openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优

文章目录 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优244.1 统计信息调优244.1.1 统计信息调优介绍244.1.2 实例分析&#xff1a;未收集统计信息导致查询性能差 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优…

4.10.CVAT——3D对象标注

文章目录 1. 创建任务2. 3D 任务工作区3.标准 3D 模式 Standard 3D mode4. 用长方体进行注释4.1. 用shapes进行注释4.2. 使用长方体进行跟踪Tracking 使用 3D 注释工具来标记 3D 对象和场景&#xff0c;例如车辆、建筑物、景观等。 1. 创建任务 要创建 3D 任务&#xff0c;您必…

快速从0-1完成聊天室开发——环信ChatroomUIKit功能详解

聊天室是当下泛娱乐社交应用中最经典的玩法&#xff0c;通过调用环信的 IM SDK 接口&#xff0c;可以快速创建聊天室。如果想根据自己业务需求对聊天室应用的 UI界面、弹幕消息、礼物打赏系统等进行自定义设计&#xff0c;最高效的方式则是使用环信的 ChatroomUIKit 。 文档地址…

面试题手撕篇

参考博客 开始之前&#xff0c;理解递归 手写 浅拷贝 function shallow(target){if(target instanceof Array){return [...resObj]}else{return Object.assign({},target);} }手写深拷贝 const _sampleDeepClone target > {// 补全代码return JSON.parse(JSON.stringify…

mybatis源码阅读系列(一)

源码下载 mybatis 初识mybatis MyBatis 是一个优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解用于配置和原始映射&#xff0c;将接口和 Java 的…

UE4_调试工具_绘制调试球体

学习笔记&#xff0c;仅供参考&#xff01; 效果&#xff1a; 步骤&#xff1a; 睁开眼睛就是该变量在此蓝图的实例上可公开编辑。 勾选效果&#xff1a;

【小白刷leetcode】第15题

【小白刷leetcode】第15题 动手刷leetcode&#xff0c;正在准备蓝桥&#xff0c;但是本人算法能力一直是硬伤。。。所以做得一直很痛苦。但是不熟练的事情像练吉他一样&#xff0c;就需要慢速&#xff0c;多练。 题目描述 看这个题目&#xff0c;说实在看的不是很懂。索性我们直…

GUROBI建模之非线性约束的处理

官方文档 目录 官方文档&#xff1a;GRBModel.AddGenConstrXxx() - Gurobi Optimization 数学规划的约束类型 基本约束(fundamental constraints)&#xff1a; 通用约束(general constraints): 1. GUROBI求解器有针对这类约束的函数&#xff0c;直接调用这类函数即可 2.…

Python-GIS分析之地理数据空间聚类

地理空间数据聚类是空间分析和地理信息系统(GIS)领域的一项关键技术。这种方法对于理解地理数据固有的空间模式和结构、促进城市规划、环境管理、交通和公共卫生等各个领域的决策过程至关重要。本文探讨了地理空间数据聚类的概念、方法、应用、挑战和未来方向。 当模式出现…

音频切割如何操作?剪辑音乐入门教程

随着数字音乐时代的来临&#xff0c;音频编辑和音乐剪辑成为了越来越多人的必备技能。无论是想要制作个人音乐作品&#xff0c;还是想要为视频添加背景音乐&#xff0c;了解如何切割和剪辑音频都是非常重要的。本文将为你提供一份音频切割和音乐剪辑的入门教程&#xff0c;帮助…

13-操作符(初识)

课前小技巧&#xff1a;VS中&#xff0c;想要复制哪一行&#xff0c;直接把鼠标放在哪一行&#xff0c;CtrlC即可&#xff0c;CtrlV直接自动复制到下一行 C语言非常灵活&#xff1a;C语言提供了非常丰富的操作符&#xff0c;使用起来比较灵活 13-1 算术操作符 - * / % 这…

WXML 模板语法

数据绑定 1. 数据绑定的基本原则 ① 在 data 中定义数据 在页面对应的 .js 文件中&#xff0c;把数据定义到 data 对象中即可 ② 在 WXML 中使用数据 2. Mustache 语法的格式 把 data 中的数据绑定到页面中渲染&#xff0c;使用 Mustache 语法&#xff08;双大括号&#x…

快速了解JavaScript

1.1 javaScript 历史 创始人 布兰登 艾奇 生于1961年 在1995设计LiveScript后改名为JavaScript 1.2 javaScript 是什么类型的语言 JavaScript是一种在客户端运行的脚本语言&#xff08;不需要编译&#xff0c;由js引擎逐行解释执行&#xff09; 1.3 JavaScript可以做什么 …

ts版本微信小程序在wxml保存文件不刷新页面的解决办法

将project.config.json中的skylineRenderEnable改为false "skylineRenderEnable": false

【理解机器学习算法】之岭回归Ridge - L2 Rgularization

Ridge 回归&#xff08;Ridge Regression&#xff09;也称作岭回归或脊回归&#xff0c;是一种专用于共线性数据分析的有偏估计回归方法。在多元线性回归中&#xff0c;如果数据集中的特征&#xff08;自变量&#xff09;高度相关&#xff0c;也就是说存在共线性(Multicollinea…

Kotlin 中List,Set,Map的创建与使用

目录 1. List 的使用 1.1 不可变 List 1.2 可变 List 2. Set 的使用 2.1 不可变 Set 2.2 可变 Set 3. Map 的使用 3.1 不可变Map 3.2 可变Map 本篇主要为已经有Java基础的同学展示Kotlin语言中的List&#xff0c;Set&#xff0c;Map的创建和使用&#xff0c;所以Java代…

STM32CubeMX与HAL库开发教程八(串口应用/轮询/中断/DMA/不定长数据收发)

目录 前言 初识串口-轮询模式 串口中断模式收发 串口DMA模式 蓝牙模块与数据包解析 前言 前面我们简单介绍过串口的原理和初步的使用方式&#xff0c;例如怎么配置和简单的收发&#xff0c;同时我们对串口有了一个初步的了解&#xff0c;这里我们来深入的来使用一下串口 …