iOS——retain和release底层原理

retain实现原理

retain的源码:

//使用此方法等价于使用[this retain]
inline id 
objc_object::retain()
{//确保对象不是tagged pointerASSERT(!isTaggedPointer());return rootRetain(false, RRVariant::FastOrMsgSend);
}
ALWAYS_INLINE id 
objc_object::rootRetain()
{//分为快速路径和慢速路径,以优化性能和处理溢出情况。return rootRetain(false, RRVariant::Fast);
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{if (slowpath(isTaggedPointer())) return (id)this;bool sideTableLocked = false;bool transcribeToSideTable = false;//为什么有isa?因为需要对引用计数+1,即retain+1,而引用计数存储在isa的bits中,需要进行新旧isa的替换isa_t oldisa;isa_t newisa;oldisa = LoadExclusive(&isa.bits);if (variant == RRVariant::FastOrMsgSend) {// 这些检查只对 objc_retain()// 他们在这里是为了避免我们重新装载isa。if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {ClearExclusive(&isa.bits);if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {return swiftRetain.load(memory_order_relaxed)((id)this);}return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));}}if (slowpath(!oldisa.nonpointer)) {// 一个类永远是一个类,所以我们可以执行一次检查// 在CAS环外if (oldisa.getDecodedClass(false)->isMetaClass()) {ClearExclusive(&isa.bits);return (id)this;}}//重点do {// 初始化 transcribeToSideTable 为 false,表示不需要将引用计数转移到 side table//这里是先设置默认情况下的transcribeToSideTabletranscribeToSideTable = false;// 将 oldisa 的值赋给 newisa,用于后续的引用计数增加操作newisa = oldisa;// 检查 newisa 是否为 nonpointer isa//nonpointer isa 是一种优化技术,用于将额外的信息编码到 isa 指针中。它和taggedPointer是不一样的if (slowpath(!newisa.nonpointer)) {// 如果 newisa 不是 nonpointer isa,清除 isa 的原子锁ClearExclusive(&isa().bits);// 如果 tryRetain 为 true,尝试 sidetable 的 tryRetain 方法if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;// 否则调用 sidetable 的 retain 方法else return sidetable_retain(sideTableLocked);}// 不检查 newisa.fast_rr,因为我们已经调用了 RR 覆盖// 检查对象是否正在被 deallocatingif (slowpath(newisa.isDeallocating())) {// 如果对象正在被 deallocating,清除 isa 的原子锁ClearExclusive(&isa().bits);// 如果 sideTableLocked 为 true,解锁 side tableif (sideTableLocked) {ASSERT(variant == RRVariant::Full);sidetable_unlock();}// 如果 tryRetain 为 true,返回 nil,表示无法增加引用计数;否则返回对象本身if (slowpath(tryRetain)) {return nil;} else {return (id)this;}}uintptr_t carry;// 增加引用计数,即对 newisa.bits 中的引用计数位进行加一操作newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++// 检查引用计数是否溢出if (slowpath(carry)) {// 如果引用计数溢出,且 variant 不为 Full,清除 isa 的原子锁并调用 rootRetain_overflowif (variant != RRVariant::Full) {ClearExclusive(&isa().bits);return rootRetain_overflow(tryRetain);}// 保留一半的引用计数在 inline,并准备将另一半复制到 side tableif (!tryRetain && !sideTableLocked) sidetable_lock();sideTableLocked = true;transcribeToSideTable = true;newisa.extra_rc = RC_HALF;newisa.has_sidetable_rc = true;}
// 尝试将 newisa 存储到 isa.bits 中,如果存储失败,则循环重新尝试
} while (slowpath(!StoreExclusive(&isa().bits, &oldisa.bits, newisa.bits)));if (variant == RRVariant::Full) {if (slowpath(transcribeToSideTable)) {// 把剩下的一半放到 side table.sidetable_addExtraRC_nolock(RC_HALF);}if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();} else {ASSERT(!transcribeToSideTable);ASSERT(!sideTableLocked);}return (id)this;
}

这段代码的逻辑是:首先初始化并赋值变量,然后进入循环,检查 newisa 是否为 nonpointer isa,如果不是,则清除 isa 的原子锁。如果 tryRetain 为真,则尝试调用 sidetable_tryRetain 增加引用计数,否则调用 sidetable_retain 增加引用计数。如果对象正在被释放,则清除 isa 的原子锁,并根据条件返回 nil 或对象。接着,尝试增加引用计数并检查是否溢出,如果溢出,则将部分引用计数转移到 side table 并设置相关标志。循环结束时,检查并处理 side table 的引用计数操作。
我们再看看sidetable_tryRetain方法:

bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
//确保对象的 isa 不是 nonpointer isa(如果支持 nonpointer isa)ASSERT(!isa().nonpointer);
#endif//从全局的sideTable中获取当前对象的sideTableSideTable& table = SideTables()[this];// NO SPINLOCK HERE// _objc_rootTryRetain() is called exclusively by _objc_loadWeak(), // which already acquired the lock on our behalf.// fixme can't do this efficiently with os_lock_handoff_s// if (table.slock == 0) {//     _objc_fatal("Do not call -_tryRetain.");// }bool result = true;//在 SideTable 的 refcnts 中尝试插入当前对象。如果插入成功,则表示该对象以前没有引用计数记录,新建的条目引用计数为 1。auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);//获取并引用映射中元素的值部分auto &refcnt = it.first->second;//如果插入了新条目(即条目不存在),什么也不做,因为条目已经初始化为 SIDE_TABLE_RC_ONE。if (it.second) {//如果条目已经存在,检查是否有 SIDE_TABLE_DEALLOCATING 标志。如果有,设置 result 为 false,表示无法增加引用计数。} else if (refcnt & SIDE_TABLE_DEALLOCATING) {result = false;//如果条目已经存在并且没有 SIDE_TABLE_DEALLOCATING 标志,再检查是否有 SIDE_TABLE_RC_PINNED 标志。如果没有,增加引用计数 SIDE_TABLE_RC_ONE。} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {refcnt += SIDE_TABLE_RC_ONE;}return result;
}

这段代码用于尝试增加对象的引用计数。
首先确保对象的 isa 不是 nonpointer isa,然后从全局的 SideTables 中获取当前对象的 SideTable。
在 SideTable 的 refcnts 中尝试插入当前对象。如果插入成功,则表示该对象以前没有引用计数记录,新建的条目引用计数为 1。如果对象已经存在于 SideTable 中,检查引用计数标志:
如果对象正在被释放(DEALLOCATING),返回 false,表示增加引用计数失败。
如果引用计数没有被固定(没有 SIDE_TABLE_RC_PINNED 标志),增加引用计数。

retain总体流程大概如下图:
请添加图片描述

release

源码:

// Equivalent to calling [this release], with shortcuts if there is no override
inline void
objc_object::release()
{ASSERT(!isTaggedPointer());rootRelease(true, RRVariant::FastOrMsgSend);
}ALWAYS_INLINE bool 
objc_object::rootRelease()
{return rootRelease(true, RRVariant::Fast);
}inline void
objc_object::release()
{ASSERT(!isTaggedPointer());if (fastpath(!ISA()->hasCustomRR())) {// Standard RR of a class is a no-op.if (!ISA()->isMetaClass())sidetable_release();return;}((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
}

ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{// 如果是标记指针,直接返回 falseif (slowpath(isTaggedPointer())) return false;bool sideTableLocked = false; // 用于标记侧表是否被锁定isa_t newisa, oldisa; // 定义 isa_t 类型的变量 newisa 和 oldisa// 加载 isa 值到 oldisa 中oldisa = LoadExclusive(&isa().bits);// 如果引用计数变种是 FastOrMsgSendif (variant == RRVariant::FastOrMsgSend) {// 这些检查仅对 objc_release() 有意义// 它们在这里是为了避免重新加载 isaif (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {ClearExclusive(&isa().bits); // 清除独占标记if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {// 如果可以调用 Swift 的引用计数方法swiftRelease.load(memory_order_relaxed)((id)this);return true;}// 调用 objc_msgSend 的 release 方法((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));return true;}}// 检查 isa 是否为指针形式if (slowpath(!oldisa.nonpointer)) {// 一个类永远是一个类,所以我们可以在 CAS 循环外进行此检查if (oldisa.getDecodedClass(false)->isMetaClass()) {ClearExclusive(&isa().bits); // 清除独占标记return false;}}#if !ISA_HAS_INLINE_RC// 如果不支持内联引用计数,使用侧表ClearExclusive(&isa().bits); // 清除独占标记return sidetable_release(sideTableLocked, performDealloc);
#else
retry:do {newisa = oldisa; // 将 oldisa 赋值给 newisaif (slowpath(!newisa.nonpointer)) {ClearExclusive(&isa().bits); // 清除独占标记return sidetable_release(sideTableLocked, performDealloc);}if (slowpath(newisa.isDeallocating())) {ClearExclusive(&isa().bits); // 清除独占标记if (sideTableLocked) {ASSERT(variant == RRVariant::Full);sidetable_unlock(); // 解锁侧表}return false;}// 不检查 newisa.fast_rr; 我们已经调用了任何 RR 重载uintptr_t carry;newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--if (slowpath(carry)) {// 不清除独占标记goto underflow;}} while (slowpath(!StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits)));if (slowpath(newisa.isDeallocating()))goto deallocate;if (variant == RRVariant::Full) {if (slowpath(sideTableLocked)) sidetable_unlock();} else {ASSERT(!sideTableLocked);}return false;underflow:// newisa.extra_rc-- 下溢:从侧表借用或析构// 放弃 newisa 以撤销递减newisa = oldisa;if (slowpath(newisa.has_sidetable_rc)) {if (variant != RRVariant::Full) {ClearExclusive(&isa().bits); // 清除独占标记return rootRelease_underflow(performDealloc);}// 将引用计数从侧表转移到内联存储if (!sideTableLocked) {ClearExclusive(&isa().bits); // 清除独占标记sidetable_lock(); // 锁定侧表sideTableLocked = true;// 需要重新开始以避免与指针非指针转换的竞争oldisa = LoadExclusive(&isa().bits);goto retry;}// 尝试从侧表中删除一些引用计数auto borrow = sidetable_subExtraRC_nolock(RC_HALF);bool emptySideTable = borrow.remaining == 0; // 如果侧表中没有引用计数,将清空侧表if (borrow.borrowed > 0) {// 侧表引用计数减少// 尝试将它们添加到内联计数bool didTransitionToDeallocating = false;newisa.extra_rc = borrow.borrowed - 1; // 重新执行原始递减newisa.has_sidetable_rc = !emptySideTable;bool stored = StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits);if (!stored && oldisa.nonpointer) {// 内联更新失败// 立即重试。这可以防止在 LL/SC 架构上发生活锁,// 因为侧表访问本身可能会丢失预留uintptr_t overflow;newisa.bits =addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);newisa.has_sidetable_rc = !emptySideTable;if (!overflow) {stored = StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits);if (stored) {didTransitionToDeallocating = newisa.isDeallocating();}}}if (!stored) {// 内联更新失败// 将保留重新放回侧表ClearExclusive(&isa().bits);sidetable_addExtraRC_nolock(borrow.borrowed);oldisa = LoadExclusive(&isa().bits);goto retry;}// 从侧表借用后的递减成功if (emptySideTable)sidetable_clearExtraRC_nolock();if (!didTransitionToDeallocating) {if (slowpath(sideTableLocked)) sidetable_unlock();return false;}}else {// 侧表最终为空,进入析构路径}}deallocate:// 真的要析构了ASSERT(newisa.isDeallocating());ASSERT(isa().isDeallocating());if (slowpath(sideTableLocked)) sidetable_unlock();__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);if (performDealloc) {this->performDealloc();}return true;
#endif // ISA_HAS_INLINE_RC
}
uintptr_t
objc_object::sidetable_release(bool locked, bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISAASSERT(!isa().nonpointer);
#endif// 获取当前对象的侧表SideTable& table = SideTables()[this];bool do_dealloc = false; // 标记是否需要析构// 如果未锁定侧表,先锁定它if (!locked) table.lock();// 尝试在侧表的引用计数字典中插入一个新条目// 如果该对象不在侧表中,插入 {this, SIDE_TABLE_DEALLOCATING}auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);auto &refcnt = it.first->second; // 获取引用计数//判断是否进行了插入操作if (it.second) {// 如果对象之前不在侧表中,表示这是第一次插入// 设置 do_dealloc 为 true,表示需要析构do_dealloc = true;} else if (refcnt < SIDE_TABLE_DEALLOCATING) {// 如果引用计数小于 SIDE_TABLE_DEALLOCATING// 表示引用计数为负数,可能设置了 SIDE_TABLE_WEAKLY_REFERENCED// 设置 do_dealloc 为 true,并将引用计数标记为 SIDE_TABLE_DEALLOCATINGdo_dealloc = true;refcnt |= SIDE_TABLE_DEALLOCATING;} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {// 如果引用计数未固定(未设置 SIDE_TABLE_RC_PINNED)// 将引用计数减一refcnt -= SIDE_TABLE_RC_ONE;}// 解锁侧表table.unlock();// 如果需要析构且 performDealloc 为真,执行析构操作if (do_dealloc && performDealloc) {this->performDealloc();}// 返回是否需要析构return do_dealloc;
}

实现了 objc_object 的引用计数减少操作,并根据引用计数的变化决定是否需要进行对象的析构。
在这里插入图片描述

dealloc

dealloc用于在对象的引用计数为0的时候,释放该对象,那么在底层,它是如何实现的呢.

inline void objc_object::rootDealloc()
{// 如果对象是 tagged pointer(标记指针),直接返回if (isTaggedPointer()) return;  // fixme necessary?#if !ISA_HAS_INLINE_RC// 如果没有内联引用计数,直接调用 object_dispose 释放对象object_dispose((id)this);
#else// 如果有内联引用计数,并且满足以下所有条件,则直接释放内存if (fastpath(isa().nonpointer &&               // 非指针 ISA!isa().weakly_referenced &&       // 没有弱引用!isa().has_assoc &&               // 没有关联对象
#if ISA_HAS_CXX_DTOR_BIT!isa().has_cxx_dtor &&            // 没有 C++ 析构函数
#else!isa().getClass(false)->hasCxxDtor() && // 没有 C++ 析构函数
#endif!isa().has_sidetable_rc))         // 没有使用 side table 引用计数{// 确认没有 side table 存在assert(!sidetable_present());// 直接释放内存free(this);} else {// 否则,调用 object_dispose 释放对象object_dispose((id)this);}
#endif // ISA_HAS_INLINE_RC
}

用于销毁和释放对象:

id object_dispose(id obj)
{// 如果 obj 是空指针,则直接返回 nilif (!obj) return nil;// 调用 objc_destructInstance 函数,销毁对象实例的内容objc_destructInstance(obj);// 释放对象所占用的内存free(obj);// 返回 nil,表示对象已被销毁return nil;
}
objc_object::clearDeallocating()
{if (slowpath(!isa.nonpointer)) {// Slow path for raw pointer isa.// 如果要释放的对象没有采用了优化过的isa引用计数sidetable_clearDeallocating();}else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {// Slow path for non-pointer isa with weak refs and/or side table data.// 如果要释放的对象采用了优化过的isa引用计数,并且有弱引用或者使用了sideTable的辅助引用计数clearDeallocating_slow();}//确保 side table 中没有该对象的引用计数记录。assert(!sidetable_present());
}

上面这段代码根据是否采用了优化过的isa做引用计数分为两种:

  1. 要释放的对象没有采用优化过的isa引用计数:会调用sidetable_clearDeallocating() 函数在 side table 中清理对象的引用计数。
void 
objc_object::sidetable_clearDeallocating()
{// 在全局的SideTables中,以this指针(要释放的对象)为key,找到对应的SideTableSideTable& table = SideTables()[this];// clear any weak table items// clear extra retain count and deallocating bit// (fixme warn or abort if extra retain count == 0 ?)table.lock();//在散列表SideTable中找到对应的引用计数表RefcountMap,拿到要释放的对象的引用计数RefcountMap::iterator it = table.refcnts.find(this);if (it != table.refcnts.end()) {//如果要释放的对象被弱引用了,通过weak_clear_no_lock函数将指向该对象的弱引用指针置为nilif (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {weak_clear_no_lock(&table.weak_table, (id)this);}//从引用计数表中擦除该对象的引用计数table.refcnts.erase(it);}table.unlock();
}

先找到对应的 SideTable,并对其加锁。
查找引用计数表 refcnts 中该对象的引用计数。
如果找到该对象的引用计数且被弱引用,则清理弱引用。
最后擦除该对象的引用计数,并解锁 SideTable。

  1. 如果该对象采用了优化过的isa引用计数并且该对象有弱引用或者使用了sideTable的辅助引用计数,就会调用clearDeallocating_slow()函数处理引用计数
NEVER_INLINE voidobjc_object::clearDeallocating_slow(){assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));// 在全局的SideTables中,以this指针(要释放的对象)为key,找到对应的SideTableSideTable& table = SideTables()[this];table.lock();if (isa.weakly_referenced) {//要释放的对象被弱引用了,通过weak_clear_no_lock函数将指向该对象的弱引用指针置为nilweak_clear_no_lock(&table.weak_table, (id)this);}//使用了sideTable的辅助引用计数,直接在SideTable中擦除该对象的引用计数if (isa.has_sidetable_rc) {table.refcnts.erase(this);}table.unlock();
}

找到对应的 SideTable,并对其加锁。
如果对象被弱引用,清理弱引用。
如果对象使用了 side table 的引用计数,擦除该对象的引用计数。
最后解锁 SideTable。

以上两种情况都涉及weak_clear_no_lock函数, 它的作用就是将被弱引用对象的弱引用指针置为nil.

dealloc的流程:如果对象是 tagged pointer,则直接返回。如果没有内联引用计数,调用 object_dispose 释放对象;如果有内联引用计数,并且对象满足非指针 ISA、没有弱引用、没有关联对象、没有 C++ 析构函数以及没有使用 side table 引用计数等条件,则直接释放内存。否则,调用 object_dispose 进行标准的对象销毁和内存释放。

请添加图片描述

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

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

相关文章

VR虚拟展厅的应用场景有哪些?

虚拟展厅作为一种利用虚拟现实技术构建的三维展示空间&#xff0c;其应用场景广泛且多样。视创云展为企业虚拟展厅搭建提供技术支持。以下是一些主要的应用场景&#xff1a; 1. 博物馆和艺术展览 文物保护与展示&#xff1a; 在博物馆中&#xff0c;为了保护珍贵的文物和艺术…

数据结构与算法学习day20-二叉树的最大深度、最小深度、完全二叉树的节点个数、平衡二叉树、二叉树所有路径

一、二叉树的最大深度 1.题目 104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 2.思路 2.1递归法 二叉树节点的深度&#xff1a;指从根节点到该节点的最长简单路径边的条数或者节点数&#xff08;取决于深度从0开始还是从1开始&#xff09;二叉树节点的高度…

【Python 学习】Pandas基础与应用(1)

题目 1 Pandas 简介1.1 主要特征1.2 Pandas 安装 2 Pandas中的数据结构2.1 Series 数据结构和操作2.1.1 Series的数据结构2.1.2 Seres的操作 2.2 DataFrame 数据结构和操作2.2.1 DataFrame 数据结构2.2.2 Dataframe 操作2.2.3 DateFrame 的特殊操作 2.3 Series 和 DataFrame 的…

Linux——网络基础Socket编程

目录 一计算机网络背景 二协议 1初始协议 1.1协议分层 1.2OSI七层模型 1.3TCP/IP五层模型 2再始协议 2.1为什么要有TCP/IP协议 2.2TCP/IP与OS的关系 2.3所以什么是协议 三网络传输基本流程 1局域网&#xff08;以太网&#xff09;通信原理 1.1认识mac地址 2同…

【牛站 / USACO2007】

题目 思路 离散化&#xff08;降低空间复杂度&#xff09; 点的编号 ∈ [ 1 , 1000 ] &#xff0c;但是点的个数最多为 2 ⋅ T ∈ [ 4 , 200 ] 点的编号 \in [1, 1000]&#xff0c;但是点的个数最多为 2 \cdot T \in[4, 200] 点的编号∈[1,1000]&#xff0c;但是点的个数最多为…

python文件自动化(4)

接上节课内容&#xff0c;在开始正式移动文件到目标文件夹之前&#xff0c;我们需要再思考一个问题。在代码运行之前&#xff0c;阿文的下载文件夹里已经存在一些分类文件夹了&#xff0c;比如图例中“PDF文件”这个文件夹就是已经存在的。这样的话&#xff0c;在程序运行时&am…

电脑硬盘数据丢失了怎么恢复?简单实用的硬盘数据找回的方法

我们的电脑使用硬盘作为存储设备来保存数据&#xff0c;硬盘里的数据是存储在扇区上&#xff0c;这些存储数据的单元则位于表面有磁性材料的旋转的盘片上。硬盘内部的磁头悬浮于高速旋转的盘片上&#xff0c;用于读写和检索数据。 假如我们使用电脑时不小心删除了某个文件&…

【B题第二套完整论文已出】2024数模国赛B题第二套完整论文+可运行代码参考(无偿分享)

2024数模国赛B题完整论文 摘要&#xff1a; 随着电子产品制造业的快速发展&#xff0c;质量控制与成本优化问题成为生产过程中亟待解决的核心挑战。为应对生产环节中的质量不确定性及成本控制需求&#xff0c;本文结合抽样检测理论和成本效益分析&#xff0c;通过构建数学模型…

ELK笔记

要搞成这样就需要钱来买服务器 开发人员一般不会给服务器权限&#xff0c;不能到服务器上直接看日志&#xff0c;所以通过ELK看日志。不让开发登录服务器。即使你查出来是开发的问题&#xff0c;费时间&#xff0c;而且影响了业务了&#xff0c;就是运维的问题 开发也不能登录…

2024国赛数学建模C题论文:基于优化模型的农作物的种植策略

大家可以查看一下35页&#xff0c;包含结构完整&#xff0c;数据完整的C题论文&#xff0c;完整论文见文末名片 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xf…

Computer Exercise

每日一练 单选题 &#xff08;     D     &#xff09; 的作用是在外界中断供电的情况下&#xff0c;及时给计算机等设备供电。 A.WPS     B.USB     C.UBS     D.UPS&#xff08;    B     &#xff09;广泛应用于精密仪器、医疗设备等对电流稳定性要求较高的场…

Unity之获取Avpro视频画面并在本地创建缩略图

一、效果 获取StreamingAssets文件夹下的所有视频&#xff08;包含其子文件夹&#xff09;&#xff0c;获取指定时间的视频画面&#xff0c;然后将图片保存到本地磁盘中。 二、关于Avpro的事件监听 当指定视频时间进度时会触发FinishedSeeking&#xff0c;代表加载完成这时我们…

fpga系列 HDL:Relu激活函数实现与仿真

代码实现对OUTPUT_NODES个32位浮点数进行RELU操作。32位浮点数的二进制表示遵循 IEEE 754 标准&#xff0c;通常称为单精度浮点数。这个标准定义了浮点数的表示方法&#xff0c;具体分为三个部分&#xff1a; 符号位 (1 bit): 用于表示浮点数的正负。&#xff08; 0 表示正数&a…

全国糖酒会,就这5个字。“会天下美味”

“全国糖酒会&#xff0c;会天下美味”&#xff0c;是全国糖酒会的品牌口号。这个品牌口号来的非常偶然。 两年前&#xff0c;全国糖酒会准备更新标志之时&#xff0c;也设计了一个品牌口号。新标志发布前几天&#xff0c;临时作了调整&#xff0c;最终变成了“全国糖酒会&…

linux下oracle启动及关于pfile和spfile启动参数文件的配置

在现代企业环境中&#xff0c;Oracle数据库作为关键的业务支撑平台&#xff0c;承载着大量的数据处理和事务管理任务。 无论是对于DBA&#xff08;数据库管理员&#xff09;还是开发人员来说&#xff0c;掌握Oracle数据库的基本操作和配置技巧都是至关重要的。本文提供了一份全…

Flutter基本组件Text使用

Text是一个文本显示控件&#xff0c;用于在应用程序界面中显示单行或多行文本内容。 Text简单Demo import package:flutter/material.dart;class MyTextDemo extends StatelessWidget {const MyTextDemo({super.key});overrideWidget build(BuildContext context) {return Sca…

Protobuf库的使用

文章目录 Protobuf是什么Protobuf使⽤流程介绍ProtoBuf的使用创建.proto⽂件指定proto3语法package声明符定义消息&#xff08;message&#xff09;编译contacts.proto⽂件命令如下&#xff1a;序列化与反序列化的使⽤ Protobuf是什么 ProtoBuf&#xff08;全称ProtocolBuffer…

【Python基础】Python函数

本文收录于 《Python编程入门》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程基础知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、函数的定义与调用三、函数参数3.1 位置参数3.2 默认参数3.3 可变数量参数&#xff08;或不定长参数…

若依框架登录鉴权详解(动态路由)

若依框架登录鉴权&#xff1a;1.获取token&#xff08;过期在响应拦截器中实现&#xff09;,2.基于RBAC模型获取用户、角色和权限信息&#xff08;在路由前置守卫&#xff09;&#xff0c;3.根据用户权限动态生成&#xff08;从字符串->组件&#xff0c;根据permission添加动…

【C++进阶】hash表的封装

文章目录 hash表哈希表的关键组成部分哈希表的优缺点优点&#xff1a;缺点&#xff1a; 常见应用场景 开放定址法实现hash表负载因子 (Load Factor)负载因子的意义负载因子的影响再散列 (Rehashing)示例 整体框架insertFinderasehash桶封装框架insertfinderase~HashTable() 总结…