【iOS】——ARC源码探究

一、ARC介绍

ARC的全称Auto Reference Counting. 也就是自动引用计数。使用MRC时开发者不得不花大量的时间在内存管理上,并且容易出现内存泄漏或者release一个已被释放的对象,导致crash。后来,Apple引入了ARC。使用ARC,开发者不再需要手动的retain/release/autorelease. 编译器会自动插入对应的代码,再结合Objective-C的runtime,实现自动引用计数。

在Objective C中,有三种类型是ARC适用的:

  • block
  • OC的对象,id, Class, NSError*等
  • 由__attribute__((NSObject))标记的类型。

像double *,CFStringRef等不是ARC适用的,仍然需要手动管理内存。

ARC 的工作原理大致是这样:当我们编译源码的时候,编译器会分析源码中每个对象的生命周期,然后基于这些对象的生命周期,来添加相应的引用计数操作代码。所以,ARC 是工作在编译期的一种技术方案,这样的好处是:

1、编译之后,ARC 与非 ARC 代码是没有什么差别的,所以二者可以在源码中共存。实际上,你可以通过编译参数 -fno-objc-arc 来关闭部分源代码的 ARC 特性。
2、相对于垃圾回收这类内存管理方案,ARC 不会带来运行时的额外开销,所以对于应用的运行效率不会有影响。相反,由于 ARC 能够深度分析每一个对象的生命周期,它能够做到比人工管理引用计数更加高效。例如在一个函数中,对一个对象刚开始有一个引用计数 +1的操作,之后又紧接着有一个 -1 的操作,那么编译器就可以把这两个操作都优化掉。
ARC 也附带有运行期的一些机制来使 ARC 能够更好的工作,主要是指 weak 关键字。weak 变量能够在引用计数为 0 时被自动设置成 nil,显然是有运行时逻辑在工作的。

ARC背后的引用计数主要依赖于这三个方法:

retain 增加引用计数
release 降低引用计数,引用计数为0的时候,释放对象。
autorelease 在当前的auto release pool结束后,降低引用计数。

二、源码探究

retain函数

inline id 
objc_object::retain()
{assert(!isTaggedPointer());if (fastpath(!ISA()->hasCustomRR())) {return rootRetain();}return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}

首先进行通过assert函数断言检查确保当前对象不是taggedPointer。因为标记指针的内存管理方式与普通对象不同。

接着检查对象的类是否具有定制的retain/release行为。如果没有,那么调用rootRetain()函数,这是一个更快的内部函数,直接更新引用计数,避免了消息发送的开销。

如果类有定制的retain/release行为,那么使用objc_msgSend发送retain消息到对象。

taggedPointer即标签指针,如果存储的是某些比较小的对象,相比于以往指向在堆上的对象的指针,taggedPointer可以直接在指针中编码数据,从而提高性能。

接着来看rootRetain函数

rootRetain()函数

ALWAYS_INLINE id 
objc_object::rootRetain()
{return rootRetain(false, false); //传递false和false参数。
}

这里会调用重载的rootRetain函数并传入两个false参数

ALWAYS_INLINE bool 
objc_object::rootTryRetain()
{return rootRetain(true, false) ? true : false; // 调用rootRetain,参数为true和false。
}

这里是尝试性调用重载的rootRetain()并传入ture和false两个参数,返回true如果成功增加引用计数,否则返回false。

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{// Inline函数,用于核心的retain操作,接受两个bool参数:// tryRetain决定是否为尝试性retain;// handleOverflow决定是否处理引用计数溢出。if (isTaggedPointer()) return (id)this; // 如果当前对象是标记指针,直接返回this,因为标记指针有自己的内存管理机制。bool sideTableLocked = false; // 初始化侧边表锁定状态为未锁定。bool transcribeToSideTable = false; // 初始化是否需要将数据转录到侧边表为否。isa_t oldisa; // 定义oldisa变量,用于存储对象的原始ISA信息。isa_t newisa; // 定义newisa变量,用于存储新的ISA信息。do {transcribeToSideTable = false; // 每次循环前,重置是否需要转录到侧边表的状态。oldisa = LoadExclusive(&isa.bits); // 使用LoadExclusive获取ISA的bits字段,保证原子操作。// LoadExclusive允许我们独占地读取内存,这样在我们读取之后和写入之前,其他线程不能修改这个内存位置。newisa = oldisa; // 复制旧的ISA信息到新ISA中,准备修改。if (slowpath(!newisa.nonpointer)) { // 检查ISA的nonpointer标志,判断是否有侧边表。// slowpath宏用于指示编译器此条件在正常情况下很少为真,用于优化。ClearExclusive(&isa.bits); // 清除独占状态,因为接下来要处理侧边表。if (!tryRetain && sideTableLocked) sidetable_unlock(); // 如果不是尝试性retain且侧边表已锁定,解锁侧边表。if (tryRetain)                  // 尝试性retain操作,调用侧边表的尝试性retain方法。return sidetable_tryRetain() ? (id)this : nil;else                            // 非尝试性retain操作,调用侧边表的常规retain方法。return sidetable_retain();}// 以上处理有侧边表的情况,接下来处理没有侧边表的情况。// 不检查newisa.fast_rr,因为我们已经调用了任何RR覆盖。// fast_rr是用于快速引用计数的字段,当有侧边表时,fast_rr字段可能无效。if (slowpath(tryRetain && newisa.deallocating)) { // 如果是尝试性retain且对象正在dealloc中。ClearExclusive(&isa.bits); // 清除独占状态,因为对象正在dealloc中。if (!tryRetain && sideTableLocked) sidetable_unlock(); // 如果不是尝试性retain且侧边表已锁定,解锁侧边表。return nil; // 返回nil,因为尝试性retain失败。}// 上面的条件判断确保我们不会在对象dealloc时尝试增加引用计数。uintptr_t carry; // 定义carry变量,用于保存进位信息。// 使用addc函数原子地增加引用计数,同时检查是否溢出。newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);if (slowpath(carry)) { // 如果addc操作产生了进位,即发生了溢出。if (!handleOverflow) { // 如果不处理溢出。ClearExclusive(&isa.bits); // 清除独占状态。// 下面调用rootRetain_overflow函数处理溢出情况,参数为tryRetain。return rootRetain_overflow(tryRetain);}// 如果需要处理溢出,准备将一半的引用计数转移到侧边表。if (!tryRetain && !sideTableLocked) sidetable_lock(); // 如果不是尝试性retain且侧边表未锁定,锁定侧边表。sideTableLocked = true; // 设置侧边表锁定状态为已锁定。transcribeToSideTable = true; // 设置需要转录到侧边表的状态为是。newisa.extra_rc = RC_HALF; // 设置extra_rc字段为RC_HALF,表示一半的引用计数。newisa.has_sidetable_rc = true; // 设置has_sidetable_rc标志,表示侧边表中有引用计数。}} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));// StoreExclusive尝试原子地更新ISA的bits字段,如果更新失败(其他线程修改了bits),则继续循环。if (slowpath(transcribeToSideTable)) { // 如果需要将数据转录到侧边表。// 转录额外的一半引用计数到侧边表,无需锁定。sidetable_addExtraRC_nolock(RC_HALF);}if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); // 如果不是尝试性retain且侧边表已锁定,解锁侧边表。// 此处的sidetable_unlock调用确保我们释放了侧边表的锁,除非正在进行尝试性retain。return (id)this; // 成功增加引用计数后,返回this指针。
}

这段代码很长大致可分为四个部分:对象是否是标记指针、对象是否有侧边表、引用计数是否会溢出、是否是尝试性retain

独占访问是指在某一时刻只有一个线程能够访问特定的资源或内存位置。这是为了避免并发访问导致的数据竞争(和不一致状态。在并发编程中,独占访问通常通过锁或原子操作来实现。

LoadExclusive函数和ClearExclusive函数就是通过原子操作来进行与独占访问有关的操作

当对象的引用计数超过了一定的阈值,通常是因为被多个引用持有,runtime会将一部分引用计数信息移动到侧边表中,以避免在对象的isa字段中存储过大的数值,从而节省空间和提高效率。

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
struct SideTable {spinlock_t slock;RefcountMap refcnts;weak_table_t weak_table;//省略其他实现...
};

这个数据结构就是存储了一个自旋锁,一个引用计数map。

在实现上,这个引用计数的map以对象的地址作为key,引用计数作为value。这意味着每个对象在侧边表中最多只有一个条目,对象的地址提供了唯一性,便于查找和更新。

release函数

inline void
objc_object::release()
{assert(!isTaggedPointer());if (fastpath(!ISA()->hasCustomRR())) {rootRelease();return;}((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}

首先进行通过assert函数断言检查确保当前对象不是taggedPointer。

接着检查对象的类是否具有定制的retain/release行为。如果没有,那么调用rootRelease()函数。

如果类有定制的retain/release行为,那么使用objc_msgSend发送release消息到对象。

rootRelease函数

ALWAYS_INLINE bool
objc_object::rootRelease()
{return rootRelease(true, false); // 调用rootRelease的重载版本,参数为true和false。
}
ALWAYS_INLINE bool
objc_object::rootReleaseShouldDealloc()
{return rootRelease(false, false); // 调用rootRelease的重载版本,参数为false和false。
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{if (isTaggedPointer()) return false; // 如果是标记指针,直接返回false,因为标记指针有自己的内存管理。bool sideTableLocked = false; // 初始化侧边表锁定状态为未锁定。isa_t oldisa; // 存储对象的原始ISA信息。isa_t newisa; // 存储新的ISA信息,用于修改。retry:do {oldisa = LoadExclusive(&isa.bits); // 使用LoadExclusive获取ISA的bits字段,开始独占访问。newisa = oldisa; // 复制旧的ISA信息到新ISA中,准备修改。if (slowpath(!newisa.nonpointer)) { // 如果有侧边表。ClearExclusive(&isa.bits); // 清除独占状态。if (sideTableLocked) sidetable_unlock(); // 如果侧边表已锁定,解锁。return sidetable_release(performDealloc); // 调用侧边表的release方法。}// 以下处理没有侧边表的情况。uintptr_t carry;newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // 原子递减引用计数,并检查是否产生借位。if (slowpath(carry)) { // 如果产生了借位,即发生了下溢。// 不ClearExclusive,保留独占访问状态。goto underflow; // 跳转到underflow标签,处理下溢情况。}} while (slowpath(!StoreReleaseExclusive(&isa.bits,oldisa.bits, newisa.bits))); // 使用StoreReleaseExclusive尝试更新ISA的bits字段,如果更新失败,继续循环。if (slowpath(sideTableLocked)) sidetable_unlock(); // 如果侧边表已锁定,解锁。return false; // 如果没有下溢,直接返回false。underflow:// 发生了下溢:从侧边表借用引用计数或析构对象。// abandon newisa以撤销递减操作。newisa = oldisa;if (slowpath(newisa.has_sidetable_rc)) { // 如果有侧边表引用计数。if (!handleUnderflow) { // 如果不处理下溢。ClearExclusive(&isa.bits); // 清除独占状态。return rootRelease_underflow(performDealloc); // 调用处理下溢的函数。}// 从侧边表转移引用计数到内联存储。if (!sideTableLocked) { // 如果侧边表未锁定。ClearExclusive(&isa.bits); // 清除独占状态。sidetable_lock(); // 锁定侧边表。sideTableLocked = true; // 设置侧边表锁定状态为已锁定。goto retry; // 重新开始循环,防止竞态条件。}// 尝试从侧边表移除一些引用计数。size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); // 从侧边表借入一半的引用计数。if (borrowed > 0) { // 如果成功借入。// 侧边表引用计数减少。// 尝试将它们添加到内联计数。newisa.extra_rc = borrowed - 1; // 重新递减引用计数。bool stored = StoreReleaseExclusive(&isa.bits,oldisa.bits, newisa.bits); // 尝试更新ISA的bits字段。if (!stored) { // 如果更新失败。// 内联更新失败。// 将借来的引用计数放回侧边表。sidetable_addExtraRC_nolock(borrowed); // 将引用计数放回侧边表。goto retry; // 重新开始循环。}// 递减成功后从侧边表借用。// 这个递减不可能是析构递减 - 侧边表锁和has_sidetable_rc标志确保如果所有其他线程都试图在我们工作时-release,最后一个会阻塞。sidetable_unlock(); // 解锁侧边表。return false; // 返回false。}else {// 侧边表为空。}}// 真正析构对象。if (slowpath(newisa.deallocating)) { // 如果对象正在析构中。ClearExclusive(&isa.bits); // 清除独占状态。if (sideTableLocked) sidetable_unlock(); // 如果侧边表已锁定,解锁。return overrelease_error(); // 返回overrelease错误。}newisa.deallocating = true; // 设置对象为正在析构中。if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; // 尝试更新ISA的bits字段,如果失败,重新开始循环。if (slowpath(sideTableLocked)) sidetable_unlock(); // 如果侧边表已锁定,解锁。__sync_synchronize(); // 确保所有线程可见状态的更新。if (performDealloc) { // 如果需要执行析构。((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); // 发送-dealloc消息。}return true; // 返回true,表示对象应该被析构。
}

这段代码大致可分为四个部分:对象是否是标记指针、对象是否有侧边表、引用计数是否会下溢、通过侧边表或析构处理下溢。

当引用计数降到0时,对象会被标记为正在析构中,并最终调用dealloc方法进行析构。如果对象有侧边表,它会尝试从侧边表借用引用计数以避免下溢。如果侧边表为空或对象已经在析构中,它会处理overrelease错误或执行析构操作。

ARC规则下autorelease

autorelease函数的作用是把对象放到autorelease pool中,到pool drain的时候,会释放池中的对象。

在ARC规则下,alloc/init/new/copy/mutableCopy开头的方法返回的对象不是autorelease对象

新建一个自定义类

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface CustomObject : NSObject
+ (instancetype)object;
- (void)dealloc;
@endNS_ASSUME_NONNULL_END
#import "CustomObject.h"@implementation CustomObject
//这个方法返回autorelease对象
+ (instancetype)object{return [[CustomObject alloc] init];
}- (void)dealloc{NSLog(@"CustomObject Dealloc");
}
@end

下面确定object方法返回的对象是不是autorelease的

#import <Foundation/Foundation.h>
#import "CustomObject.h"
int main(int argc, const char * argv[]) {__weak CustomObject * weakRef;{CustomObject * temp = [CustomObject object];weakRef = temp;}NSLog(@"%@",weakRef);
}

在这里插入图片描述

这是因为[CustomObject object]返回的不是一个autorelease对象,在作用域(大括号)结束后,并不会立刻被释放,所以在NSLog处还能看到对象的地址。

如果把[CustomObject object]替换成[[CustomObject alloc] init],会发现作用域结束后立刻释放。

在这里插入图片描述

假如我们用autorelease包裹后:

    __weak CustomObject * weakRef;@autoreleasepool {CustomObject * temp = [CustomObject object];weakRef = temp;}NSLog(@"%@",weakRef);

在这里插入图片描述

会看到dealloc方法先调用。

放到自动释放池的对象是在超出自动释放池作用域后立即释放的。事实上在iOS 程序启动之后,主线程会启动一个Runloop,这个Runloop在每一次循环是被自动释放池包裹的,在合适的时候对池子进行清空。

对于Cocoa框架来说,提供了两种方式来把对象显式的放入AutoReleasePool.

  • NSAutoreleasePool(只能在MRC下使用)
  • @autoreleasepool {}代码块(ARC和MRC下均可以使用)

下面从源码入手分析autuorelease

autorelease函数

inline id 
objc_object::autorelease()
{if (isTaggedPointer()) return (id)this;if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease();return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease);
}

首先进行通过assert函数断言检查确保当前对象不是taggedPointer。

接着检查对象的类是否具有定制的retain/release行为。如果没有,那么调用rootAutorelease()函数。

如果类有定制的retain/release行为,那么使用objc_msgSend发送autorelease()消息到对象。

inline id 
objc_object::rootAutorelease()
{if (isTaggedPointer()) return (id)this;if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;return rootAutorelease2();
}

首先,函数检查对象是否是taggedPointer。如果是taggedPointer,那么 autorelease 操作不需要做任何事情,因为taggedPointer的对象通常较小且生命周期管理是内置的。在这种情况下,函数简单地返回 this 指针。

接下来,函数调用 prepareOptimizedReturn(ReturnAtPlus1) 函数。这个函数检查是否可以在当前调用栈深度 + 1 的地方直接返回,从而跳过自动释放池的操作。这通常在某些优化场景下发生,比如当对象在当前作用域结束时就会被销毁,那么就没有必要将其放入自动释放池中等待稍后的释放。如果可以优化,函数再次直接返回 this 指针。

如果上述两种情况都无法应用,那么函数会调用 rootAutorelease2() 函数。rootAutorelease2() 是 autorelease 操作的核心实现,负责将对象放入当前线程的自动释放池中,以便在适当的时候(通常是作用域结束时)释放对象。

rootAutorelease2函数

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{assert(!isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);
}

首先判断对象是否为taggedPointer,如果不是就调用AutoreleasePoolPage::autorelease这个方法把该对象作为参数。

AutoreleasePoolPage::autorelease函数

public: static inline id autorelease(id obj)
{// 断言确保传入的对象非空。assert(obj);// 确保对象不是标记指针,标记指针不需要自动释放。assert(!obj->isTaggedPointer());// 调用快速自动释放函数,尝试将对象加入当前热自动释放池页面。id *dest __unused = autoreleaseFast(obj);// 断言检查,确保dest要么是空,要么是空池占位符,要么指向obj。// 这里用于验证autoreleaseFast是否正确执行。assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);// 返回原始对象,autorelease操作完成后对象仍然可用。return obj;
}// 快速自动释放实现,尝试将对象加入到当前线程的热自动释放池页面。
static inline id *autoreleaseFast(id obj)
{// 获取当前线程的热自动释放池页面。AutoreleasePoolPage *page = hotPage();// 如果页面存在并且没有满,则尝试将对象加入到页面中。if (page && !page->full()) {return page->add(obj);}// 如果页面存在但已满,调用特殊处理函数处理满页情况。else if (page) {return autoreleaseFullPage(obj, page);}// 如果页面不存在,调用无页面自动释放处理函数。else {return autoreleaseNoPage(obj);}
}// 自动释放池页面的add方法,用于向页面中添加一个对象。
id *AutoreleasePoolPage::add(id obj)
{// 确保页面没有满。assert(!full());// 临时解除保护,允许修改页面。unprotect();// 记录下一个要写入的位置,这里使用next指针。// 注意返回的是next-1的地址,但通过直接返回next避免了指针偏移。id *ret = next;// 将对象写入到next指向的位置,然后递增next。*next++ = obj;// 重新保护页面,防止其他线程修改。protect();// 返回对象在页面中的地址,用于验证。return ret;
}

autoreleaseFast函数尝试将对象加入到当前线程的“热”自动释放池页面。如果页面存在并且没有满,对象将被直接加入到页面中。如果页面已满,或者页面不存在,将分别调用autoreleaseFullPageautoreleaseNoPage函数分别进行满页和无页的处理。

AutoreleasePoolPage::add方法负责将对象实际添加到页面中。它先解除页面保护,将对象写入到指定位置,然后递增页面的写入指针,最后重新保护页面。这里的保护和解除保护操作是为了确保在多线程环境下页面数据的一致性和安全性。

autorelease方法会把对象存储到AutoreleasePoolPage的双向链表里。等到autorelease pool被drain的时候,把链表内存储的对象删除。所以AutoreleasePoolPage就是自动释放池的内部实现。

一个 poolPage 的大小 是 4096 字节。其中56 bit 用来存储其成员变量,剩下的存储加入到自动释放池中的对象。原因在于在现代操作系统中,内存通常以页面为单位进行分配和管理,而页面的大小通常为4096字节。因此,将自动释放池页面的大小设定为4096字节可以很好地与操作系统内存管理机制对齐

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

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

相关文章

BUUCTF逆向wp [HDCTF2019]Maze

第一步 查壳&#xff0c;本题是32位&#xff0c;有壳&#xff0c;进行脱壳。 第二步 这里的 jnz 指令会实现一个跳转&#xff0c;并且下面的0EC85D78Bh被标红了&#xff0c;应该是一个不存在的地址&#xff0c;这些东西就会导致IDA无法正常反汇编出原始代码&#xff0c;也称…

启智畅想火车类集装箱号码识别技术,软硬件解决方案

集装箱号码识别需求&#xff1a; 实时检测车皮号、火车底盘号码、集装箱号码&#xff0c;根据火车类型分为以下三种情况&#xff1a; 1、纯车皮&#xff0c;只检测车皮号&#xff1b; 2、火车拉货箱&#xff08;半车皮&#xff09;&#xff0c;检测车皮号集装箱号码&#xff1b…

如何从0搭建一个Ai智体day01

&#x1f4da;《AI破局行动&#xff5c;AI智能体&#xff08;coze&#xff09;实战手册》&#xff1a; https://d16rg8unadx.feishu.cn/wiki/XQESwHW5HiPFlrkZbkqc0Xp7nEb 说明 这个是授权访问的&#xff0c;想学习加我 微信/ Github:** watchpoints &#x1f4fa;Day1-大圣直播…

玩转HarmonyOS NEXT之常用布局三

轮播&#xff08;Swiper&#xff09; Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件&#xff0c;当设置了多个子组件后&#xff0c;可以对这些子组件进行轮播显示。通常&#xff0c;在一些应用首页显示推荐的内容时&#xff0c;需要用到轮播显示的能力。 针对…

Xcode 16 beta3 真机调试找不到 Apple Watch 的尝试解决

很多小伙伴们想用 Xcode 在 Apple Watch 真机上调试运行 App 时却发现&#xff1a;在 Xcode 设备管理器中压根找不到对应的 Apple Watch 设备。 大家是否已将 Apple Watch 和 Mac 都重启一万多遍了&#xff0c;还是束手无策。 Apple Watch not showing in XCodeApple Watch wo…

NineData全面支持PostgreSQL可视化表结构设计

“PostgreSQL 是最像 Oracle 的开源关系型数据库“&#xff0c;也正因为如此&#xff0c;很多企业都青睐 PostgreSQL&#xff0c;拿它当成 Oracle 的替代品。所以毫无疑问&#xff0c;目前 PostgreSQL 在企业中非常常见。 对于直接接触 PostgreSQL 的开发人员而言&#xff0c;…

快速排序及归并排序的实现与排序的稳定性

目录 快速排序 一. 快速排序递归的实现方法 1. 左右指针法 步骤思路 为什么要让end先走&#xff1f; 2. 挖坑法 步骤思路 3. 前后指针法 步骤思路 二. 快速排序的时间和空间复杂度 1. 时间复杂度 2. 空间复杂度 三. 快速排序的优化方法 1. 三数取中优化 2. 小区…

实验丰富、原创改进!|多策略改进蜣螂优化算法(MATLAB)

本文内容来源于本人公众号&#xff1a;KAU的云实验台&#xff0c;更新内容&#xff1a;智能优化算法及其改进应用。 本文核心内容&#xff1a; 新颖的多策略改进蜣螂优化算法 对比算法包括&#xff1a;高引用/新发布/经典/其他DBO变体&#xff08;共11种&#xff09; 实验设计…

SpringBoot结合ip2region实现博客评论显示IP属地

你好呀&#xff0c;我是小邹。 在现代的Web应用中&#xff0c;特别是博客和论坛类网站&#xff0c;为用户提供地理定位服务&#xff08;如显示用户所在地理位置&#xff09;可以极大地增强用户体验。本文将详细探讨如何使用Java和相关技术栈来实现在博客评论中显示用户的地址信…

Java实验3

实验内容 学生信息管理系统 学生成绩表Student(Sno 字符串&#xff0c;长度9, Sname 字符串&#xff0c;长度10, Class 字符串&#xff0c;长度10, Age 整型, Sex 字符串&#xff0c;长度2) 实现如下功能&#xff1a; A&#xff0e;输入若干个学生的信息到Student表&#x…

【NetTopologySuite类库】合并所有几何的包围盒AABB

流程示意图 示例代码 using GeoAPI.Geometries; using Microsoft.VisualStudio.TestTools.UnitTesting; using NetTopologySuite.Geometries; using NetTopologySuite.IO; using System.Collections.Generic; using System.Linq;namespace Test472 {[TestClass]public class T…

深度解析:电商订单API及其技术实现

随着电子商务的发展&#xff0c;实体企业开拓电商渠道的越来越多&#xff0c;原有的管理系统都需要增加电商业务管理功能&#xff0c;其中&#xff0c;对电商订单的管理是每一个电商商家都需要的功能&#xff0c;所以对于开发者来说&#xff0c;了解电商API是什么是非常重要的&…

第100+16步 ChatGPT学习:R实现Xgboost分类

基于R 4.2.2版本演示 一、写在前面 有不少大佬问做机器学习分类能不能用R语言&#xff0c;不想学Python咯。 答曰&#xff1a;可&#xff01;用GPT或者Kimi转一下就得了呗。 加上最近也没啥内容写了&#xff0c;就帮各位搬运一下吧。 二、R代码实现Xgboost分类 &#xff08…

C++动态内存的管理

今天来分享C动态内存管理相关知识&#xff0c;闲言勿谈&#xff0c;直接上干货。 1. 动态内存的开辟和销毁(new和delete) (1)前置知识&#xff1a;我们知道c语言有malloc和calloc和realloc三个函数可以进行动态的开辟内存&#xff0c;那么它们有什么区别呢&#xff1f;首先是…

PLC_博图系列☞基本指令”TONR:时间累加器“

PLC_博图系列☞基本指令”TONR&#xff1a;时间累加器“ 文章目录 PLC_博图系列☞基本指令”TONR&#xff1a;时间累加器“背景介绍TONR&#xff1a; 时间累加器说明参数脉冲时序图示例 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 TONR 背景介绍 这是一篇关于P…

昇思25天学习打卡营第24天|基于MindSpore的Diffusion扩散模型

Diffusion扩散模型 本文基于Hugging Face&#xff1a;The Annotated Diffusion Model一文翻译迁移而来&#xff0c;同时参考了由浅入深了解Diffusion Model一文。 关于扩散模型&#xff08;Diffusion Models&#xff09;有很多种理解&#xff0c;本文的介绍是基于denoising di…

基础动态规划题目基础动态规划题目

目录 题目1&#xff1a; P1216 [USACO1.5] [IOI1994]数字三角形 Number Triangles 代码示例&#xff1a; 题目2&#xff1a; Common Subsequence 代码示例 题目3 &#xff1a;最长上升子序列 最长不下降子序列 最长上升子序列oj答案 题目1&#xff1a; P1216 [USACO1.5]…

SQL面试题练习 —— 查询每个用户最大连续登录天数

目录 1 题目2 建表语句3 题解 1 题目 查询每个用户最大连续登录天数 样例数据如下 login_log&#xff1a; 2 建表语句 --建表语句 create table if not exists login_log (user_id int comment 用户id,login_time date comment 登录时间 ); --数据插入 INSERT overwrit…

Matlab进阶绘图第63期—带标记线的三维填充折线图

三维填充折线图是在三维折线图的基础上&#xff0c;对其与XOY平面之间的部分进行颜色填充&#xff0c;从而能够更好地刻画细节变化。 而带标记线的三维填充折线图是在其基础上&#xff0c;添加X相同的一条或多条标记线&#xff0c;以用于进一步讨论分析。 由于Matlab中未收录…

飞睿智能UWB Tag蓝牙防丢器标签,宠物安全新升级,5cm精准定位测距不迷路

宠物早已成为许多家庭不可或缺的一员&#xff0c;它们用无条件的爱温暖着我们的心房&#xff0c;陪伴我们度过每一个平凡而温馨的日子。然而&#xff0c;随着宠物活动范围的扩大和外界环境的复杂多变&#xff0c;宠物走失的风险也随之增加。每一次出门遛弯&#xff0c;都像是心…