【iOS】——ARC源码探究

news2024/9/25 3:25:58

一、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;
@end

NS_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.coloradmin.cn/o/1931896.html

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

相关文章

docker搭建普罗米修斯监控gpu

ip8的服务器监控ip110和ip111的服务器 被监控的服务器110和111只需要安装node-export和nvidia-container-toolkit 下载镜像包 docker pull prom/node-exporter docker pull prom/prometheus docker pull grafana/grafana新建目录 mkdir /opt/prometheus cd /opt/prometheus/…

微软的vscode和vs2022快捷键官网链接

vscode官方文档:https://code.visualstudio.com/docs/ vscode快捷键官方文档:https://code.visualstudio.com/docs/getstarted/keybindings vs2022官方文档:https://learn.microsoft.com/zh-cn/visualstudio/ide/?viewvs-2022 vscode快捷键官方文档:https://learn.microsoft.c…

石头剪刀布休息(猜拳游戏)

自己写的简易版 //2024.07.17 import java.util.Scanner; import java.util.Random; public class GuessingGame {public static void main(String[] args) {Tom tm new Tom();System.out.println("");for (int i 0; i < 3; i) {Random r new Random();tm.com…

数据可视化在智慧医疗中的重要应用

在现代智慧医疗的推动下&#xff0c;数据可视化技术正日益成为医疗领域的重要工具。通过将复杂的医疗数据转换为直观的图表和图形&#xff0c;数据可视化不仅提升了医疗服务的效率&#xff0c;还极大地改善了患者的就医体验。 在智慧医疗中&#xff0c;数据可视化首先在电子病历…

Android View的绘制流程

1.不管是View的添加&#xff0c;还是调用View的刷新方法invalidate()或者requestLayout()&#xff0c;绘制都是从ViewRootImpl的scheduleTraversals()方法开始 void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled true;mTraversalBarrier mHandler…

什么牌子的充电宝小巧容量大!盘点小巧性价比高充电宝!

想象一下&#xff0c;当准备轻装出门&#xff0c;无论是逛街购物、户外运动&#xff0c;还是长途旅行&#xff0c;一个小巧玲珑却又容量充足的充电宝&#xff0c;能轻松地被塞进的口袋、背包的小角落里&#xff0c;随时随地为的电子设备“续命”&#xff0c;这是多么令人欣喜的…

Tita的OKR:高端制造行业的OKR案例

高端设备制造行业的发展趋势&#xff1a; 产业规模持续扩大&#xff1a;在高技术制造业方面&#xff0c;航空、航天器及设备制造业、电子工业专用设备制造等保持较快增长。新能源汽车保持产销双增&#xff0c;新材料新产品生产也高速增长。 标志性装备不断突破&#xff1a;例如…

sqlite数据库,轻量级数据库的使用

什么是sqlite数据库 sqlite是具有零配置、无服务的特点&#xff0c;遵循 ACID 规则&#xff0c;是一款备受欢迎的轻量级数据库。 tips&#xff1a;ACID 规则即&#xff0c;A&#xff08;原子性&#xff09;、C&#xff08;一致性&#xff09;、I&#xff08;独立性&#xff0…

第一百六十七节 Java IO教程 - Java Zip字节数组

Java IO教程 - Java Zip字节数组 校验和 Java在java.util.zip包中提供了一个Adler32类来计算数据字节的Adler-32校验和。 我们需要调用这个类的update()方法将字节传递给它。 在同一个包中还有另一个名为CRC32的类&#xff0c;它允许您使用CRC32算法计算校验和。 以下代码…

2.10、matlab中字符、数字、矩阵、字符串和元胞合并为字符串并将字符串以不同格式写入读出excel

1、前言 在 MATLAB 中&#xff0c;可以使用不同的数据类型&#xff08;字符、数字、矩阵、字符串和元胞&#xff09;合并为字符串&#xff0c;然后将字符串以不同格式写入 Excel 文件。 以下是一个示例代码&#xff0c;展示如何将不同数据类型合并为字符串&#xff0c;并以不…

Java的maven项目pom文件因为版本问题导入失败的处理办法

前言 今天遇到的一个问题&#xff0c;而且网上的答案很乱&#xff0c;几乎每人各执一词&#xff0c;年份还差距很远&#xff0c;几乎没有为我解决问题提供什么帮助&#xff0c;感觉蛮怪的&#xff0c;确实不常见&#xff0c;但不应该每次都靠降低版本来解决这种小类小bug&…

【文心智能体】前几天百度热搜有一条非常有趣的话题《00后疯感工牌》,看看如何通过低代码工作流方式实现图片显示

00后疯感工牌体验&#xff1a;https://mbd.baidu.com/ma/s/6yA90qtM 目录 前言比赛推荐工作流创建工作流入口创建工作流界面工作流界面HTTP工具卡点地方 总结推荐文章 前言 前几天百度热搜有一条非常有有趣《00后疯感工牌》。 想着通过文心智能体去一键生成00后疯感工牌是不是…

防火墙的冗余基础知识+实验检测

将之前先理清需要注意的知识点&#xff1a; 1、注意防火墙冗余时的会话表必须保持一致&#xff0c;这里HRP技术已经做到 2、vrrp是自动开启抢占的&#xff0c;且是根据优先级进行抢占的 3、免费ARP的作用&#xff1a;告诉交换机的某个IP的mac地址变成了我的这个mac地址 4、HRP …

Calibration相机内参数标定

1.环境依赖 本算法采用张正友相机标定法进行实现&#xff0c;内部对其进行了封装。 环境依赖为 ubuntu20.04 opencv4.2.0 yaml-cpp yaml-cpp安装方式&#xff1a; &#xff08;1&#xff09;git clone https://github.com/jbeder/yaml-cpp.git #将yaml-cpp下载至本地 &a…

Sentinel规则持久化Push模式两种实现方式

文章目录 sentinel持久化push推模式微服务端的实现具体实现源码分析读数据源写数据源的实现 微服务端解析读数据源流程 修改源码的实现官方demo修改源码实现配置类flowauthoritydegreadparamsystemgateway修改源码 测试补充 前置知识 pull模式 sentinel持久化push推模式 pull拉…

效率飙升!用升级版思维导图搞定测试用例

Xmind思维导图&#xff1c;转&#xff1e;测试用例_如何将xmind改成测试用例-CSDN博客https://weiyv.blog.csdn.net/article/details/135920569 上一次的【xmind思维导图转测试用例】的文章浏览量飙升&#xff0c;这一次把工具又进行升级啦&#xff0c;是在线版的免费工具哦&am…

JRebelXRebel在线激活(亲测可用)

包含所有新旧版本&#xff0c;包括2023.4.2、2023.4.1、2023.4.0、2023.3.2、2023.3.1、2023.3.0、2023.2.2、2023.2.1、2023.2.0、2023.1.2、2023.1.1 等以及所有2022版本 JRebel&XRebel激活服务器地址 激活服务器地址&#xff08;路线1,推荐&#xff09;&#xff0c;可…

文本到 3D AI 生成——Meta 3D Gen、OpenAI Shap-E工作原理与算法解析

概述 根据文本提示生成 3D 数字资产的能力代表了人工智能和计算机图形学领域最近最令人兴奋的发展之一。随着 3D 数字资产市场规模预计将从2024 年的 28.3 亿美元增加到 2029 亿美元&#xff0c;文本转 3D 人工智能模型将在游戏、电影、电子商务等行业的内容创作革命中发挥重要…

【论文阅读】(StemGNN)多元时间序列预测的谱时间图神经网络

&#xff08;StemGNN&#xff09;Spectral Temporal Graph Neural Network for Multivariate Time-series Forecasting 引用&#xff1a; Cao D , Wang Y , Duan J ,et al.Spectral Temporal Graph Neural Network for Multivariate Time-series Forecasting[J]. 2021.DOI:10.…

揭秘“循环乐购”:消费赠礼,每日返利

大家好&#xff0c;我是吴军&#xff0c;今天作为您的电商策略顾问&#xff0c;将带您深入探索一种前所未有的商业模式——“循环乐购”。在这个模式中&#xff0c;消费不再是单向支出&#xff0c;而是成为了开启财富增值的钥匙。您是否好奇&#xff0c;为何有人能在享受购物乐…