iOS——MRC与ARC以及自动释放池深入底层学习

news2024/12/27 13:16:43

MRC与ARC再回顾

在前面,我们简单学了MRC与ARC。MRC指手动内存管理,需要开发者使用retainrelease等手动管理对象的引用计数,确保对象在必要时被释放。ARC指自动内存管理,由编译器自动管理对象的引用计数,开发者不需要手动管理内存。

哪些对象需要我们进行内存管理呢?

  • 任何继承了NSObject的对象需要进行内存管理
  • 而其他非对象类型(int、char、float、double、struct、enum等) 不需要进行内存管理

这是因为

  • 继承了NSObject的对象的存储在操作系统的堆里边。而操作系统的堆一般由程序员分配释放。
  • 非OC对象一般放在操作系统的栈里面,操作系统的栈由操作系统自动分配释放。

例:

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        int a = 10; // 栈
        int b = 20; // 栈
        // Person对象(计数器==1) : 堆
        Person *p = [[Person alloc] init];
        // p(局部指针变量): 栈
    }
    // 经过上面代码后, 栈里面的变量a、b、p 都会被回收
    // 但是堆里面的Person对象还会留在内存中,因为它是计数器依然是1
    return 0;
}

MRC深入

首先,先回顾空指针和野指针;

空指针、野指针

空指针:

空指针指的是没有指向存储空间的指针(里面存的是 nil, 也就是 0)。
给空指针发消息是没有任何反应的,也不会报错

空指针比如:

NSObject *a = [[NSObject alloc] init];   //执行完引用计数为1
[a release];   //执行完引用计数为 0,实例对象被释放。
a = nil;   //此时,a变为了空指针。
[a release];   // 再给空指针a发送消息就不会报错了。
[a release];  // 这段代码不会报错

野指针:

只要一个对象被释放了,我们就称这个对象为“僵尸对象(不能再使用的对象)”。
当一个指针指向一个僵尸对象,我们就称这个指针为「野指针」。
只要给一个野指针发送消息就会报错(EXC_BAD_ACCESS 错误)。

MRC避免循环引用

定义两个了类Person和Dog
Person类:

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class Dog;
@interface Person : NSObject
@property (nonatomic, assign) Dog *dog;
@end

NS_ASSUME_NONNULL_END

Dog类:

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class Person;
@interface Dog : NSObject
@property (nonatomic, retain) Person *person;
@end

NS_ASSUME_NONNULL_END

主函数:

Person *p = [[Person alloc] init];
Dog *d = [[Dog alloc] init];
        
p.dog = d;
d.person = p;
        
[p release];
[d release];

我们看上面的代码,会出现 A 对象要拥有 B 对象,而 B 对应又要拥有 A 对象,此时会形成循环 retain,导致 A 对象和 B 对象永远无法释放。

要解决这个问题的话:不要让 A retain B,B retain A,所以其中一方不要做retain方法。当两端互相引用时,应该一端用 retain,一端用 assign。

ARC深入

在之前的博客里,已经学习了ARC许多知识,包括修饰符、规则、属性和简单实现。可以看我的博客:iOS——【自动引用计数】ARC规则及实现
现在我们更深入的了解它的实现。

在Objective C中,有三种类型是ARC适用的:
1. block
2. objective 对象,id, Class, NSError*等
3. 由attribute((NSObject))标记的类型。

*像double ,CFStringRef等不是ARC适用的,仍然需要手动管理内存。
Tips: 以CF开头的(Core Foundation)的对象往往需要手动管理内存。

ARC在编译期和运行期分别做了什么?

先说结论:

1. 在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
2. ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。

接下来我们来探究这个结论:

ARC的实现

ARC的底层是怎么工作的

首先根据前面的学习,我们知道,ARC有一套命名规则:若方法名以alloc、new、copy、mutableCopy开头,则方法的调用者需要负责保留和释放使用该方法返回的对象,在MRC中,就需要我们手动保留和释放返回的对象。如果不以上面这些开头,则不用调用者保留和释放,因为方法内部会自动执行autorelease方法。

下面用实例说明:
实例1
首先,我们创建一个Person类,这个类有两个类方法,一个是用creat开头的,一个是用new开头的,现在调用这两个方法:

#import "Person.h"

@implementation Person

+ (instancetype)creatPerson {
    return [[self alloc] init];
}
+ (instancetype)newPerson {
    return [[self alloc] init];
}

@end
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    [Person creatPerson];
    [Person newPerson];
}

接下来我们查看它的汇编代码:
在这里插入图片描述

可以看见,在newPerson方法下,首先用objc_megSendnewPerson发送消息,然后有一个objc_release的标志位,这个标志位意味着系统在这个位置会调用objc_release函数。
而在creatPerson方法下,creatPerson中ARC修改后的代码大致逻辑是:

//Person.m
+ (instancetype)creatPerson {
    id temp = [self new];  
    return objc_autoreleaseReturnValue(temp); 
} 
//main.m
- (void)testForARC { 
    objc_unsafeClaimAutoreleasedReturnValue([Person creatPerson]); 
}

在create中多了objc_autoreleaseReturnValue,这是因为ARC规则,无需手动释放的内部自动autorelease。
objc_autoreleaseReturnValue: 这个函数的作用相当于代替我们手动调用 autorelease, 创建了一个autorelease对象。编译器会检测之后的代码, 根据返回的对象是否执行 retain操作, 来设置全局数据结构中的一个标志位, 来决定是否会执行 autorelease操作。该标记有两个状态, ReturnAtPlus0代表执行 autorelease, 以及ReturnAtPlus1代表不执行 autorelease。
objc_unsafeClaimAutoreleasedReturnValue函数作用是对autorelease对象不做处理仅仅返回,对非autorelease对象调用objc_release函数并返回。所以本情景中它创建时执行了 autorelease操作了,就不会对其进行 release操作了。只是返回了对象,在合适的实际autoreleasepool会对其进行释放的。

实例2
我们再给出下面的例子:

id temp2 = [Person newPerson];

查看汇编代码:
在这里插入图片描述

可以看出来,与前面不同的是多了一个objc_storeStrong的标志位。因此可知,编译器在这里调用了该函数。我们在objc库里看看这个函数的实现。在objc4的NSObject.mm中:

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

说一下这个函数:objc_storeStrong函数是用于管理强引用的一个函数,主要用于更新一个指向对象的强引用,并确保正确的内存管理。
在上面这段代码里:首先获取旧对象的引用(prev)。然后进行比较,如果新对象和旧对象相同,则返回。否则,保留新对象,并将新对象的引用+1。然后更新指针*location,指向新对象。最后释放旧对象。使用这个函数是因为:temp2是一个强引用变量。编译器会将上述代码转换为类似以下内容:

id newObject = [Person newPerson];
objc_storeStrong(&temp2, newObject);

而同样的,在creatPerson方法中:

id temp1 = [Person creatPerson];

它的汇编代码:
在这里插入图片描述

这里同样使用了objc_storeStrong函数,但是多了一个objc_retainAutoreleasedReturnValue函数,这个函数将替代 MRC中的 retain方法, 此函数也会检测刚才提到的那个标志位, 如果为ReturnAtPlus0执行该对象的 retain操作,否则直接返回对象本身。

在这个例子中, 由于代码中没有对对象进行保留, 所以创建时objc_autoreleaseReturnValue函数设置的标志位状态是应该是ReturnAtPlus0。所以, 该函数在此处是会进行 retain操作的。
实例3
有如下代码:

#import "strat.h"
#import "Person.h"

@interface strat ()

@property (nonatomic, strong) id temp1;
@property (nonatomic, strong) id temp2;

@end

@implementation strat

- (void)gogo {
    self.temp1 = [Person creatPerson];
    self.temp2 = [Person newPerson];
}

@end

我们先看temp2的汇编代码:
在这里插入图片描述

可以看出,这段代码相比于实例2的代码,多了一个megSend:objc_msgSend$setTemp2,这个是setter方法的函数,所以其实这里相当于多了一个setter操作。因此这里ARC的补充的代码大概是:

- (void)setTemp2:(id)newValue {
    objc_storeStrong(&_temp2, newValue);
}

temp1与temp2被补充的部分差不多。

自动释放池

自动释放池(autorelease)是 Objective-C 内存管理的重要特性之一。
简单来说它允许开发者推迟对象的释放时间,通常在下一个事件循环中才执行释放操作。
自动释放池之前在博客 中学习过,现在来深入学习一下。

MRC下的自动释放池

在MRC下使用自动释放池是通过NSAutoreleasePool实现的(如果编译器版本是LLVM.3.0以上,那么@autorelease{…}也可以使用),其生命周期相当于c语言变量的作用域。对于所有调用过 autorelease方法的对象,在废弃 NSAutoreleasePool对象时,都将调用 release实例方法。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建并使pool持有一个自动释放池
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        //创建Person实例对象p
        Person *p = [[Person alloc] init];
        //将实例对象p加入pool持有的自动释放池
        [p autorelease];
        //废弃NSAutoreleasePool对象;
        //并向pool池中的所有对象发送消息,让它们调用release方法。这里相当于也[p release]了
        [pool drain];
        
        //这里会报错,因为p已被释放
        NSLog(@"%@", p);
    }
    return 0;
}

报错内容:Thread 1: EXC_BAD_ACCESS (code=1, address=0x370001a2b46808)

这里我当时有一个问题:当调用[p autorelease]的时候,是怎么判断当前的自动释放池是pool持有的那个的?
这是因为:运行时系统维护了一个全局的栈来跟踪所有的自动释放池。当一个新的 NSAutoreleasePool 对象被创建时,它会被推入栈顶。随后,当调用 -autorelease 方法时,当前的栈顶的自动释放池会接收该对象。

理解 NSAutoreleasePool对象的生命周期,如下图所示:
在这里插入图片描述

ARC下使用自动释放池

ARC环境不能使用 NSAutoreleasePool类也不能调用autorelease方法,代替它们实现对象自动释放的是 @autoreleasepool块和__autoreleasing修饰符。比较两种环境下的代码差异如下图:
在这里插入图片描述

ARC环境下使用自动释放池:

@autoreleasepool {
    id obj = [[NSObject alloc] init];
    NSLog(@"%@", obj); 
}

ARC的很多情况下,即使是不显式的使用 __autoreleasing,也能实现对象被注册到释放池中。主要包括以下几种情况:

编译器会进行优化,检查方法名是否以 alloc/new/copy/mutableCopy开始,如果不是则自动将返回对象注册到 Autoreleasepool;
访问附有 __weak修饰符的变量时,实际上必定要访问注册到 Autoreleasepool的对象,即会自动加入 Autoreleasepool;
id的指针或对象的指针(id*,NSError **),在没有显式地指定修饰符时候,会被默认附加上 __autoreleasing修饰符,加入 Autoreleasepool。

AutoreleasePool的具体实现

有以下自动释放池的代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"hello");
    }
    return 0;
}

将其转化为cpp代码:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_mr_nr_xs_2x079d0zxp68ymmm4c0000gn_T_main_82a790_mi_0);
    }
    return 0;
}

可以发现,自动释放池在底层中对应的是一个__AtAutoreleasePool的结构体,我们再在源码中可以找到这个结构体的定义:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {
    atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
  ~__AtAutoreleasePool() {
    objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
  // 边界对象(指针),用来标识自动释放池的开始和结束。这个对象在创建和销毁自动释放池时分别被推入和弹出。
  void * atautoreleasepoolobj;
};

可以看见,在这个结构体中有一个构造函数内部调用objc_autoreleasePoolPush(),和一个析构函数内部调用objc_autoreleasePoolPop(atautoreleasepoolobj),以及一个atautoreleasepoolobj边界对象。

  • objc_autoreleasePoolPush()用于返回边界对象
  • objc_autoreleasePoolPop(atautoreleasepoolobj)用于传入边界对象

边界对象其实就是 nil的别名,而它的作用事实上也就是为了起到一个标识的作用。

AutoreleasePoolPage

我们继续深入对objc_autoreleasePoolPush()objc_autoreleasePoolPop(atautoreleasepoolobj)进行学习,发现它们实际上是对AutoreleasePoolPage对应的静态方法 push和 pop的封装。
在NSObject的源码中可以发现以下代码:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

AutoreleasePoolPage是C++的一个类,在NSObject.mm中可以找到它的代码,这是它的一部分核心代码:

class AutoreleasePoolPage {
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)  //空池占位
#   define POOL_BOUNDARY nil                //边界对象(即哨兵对象)
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);
    magic_t const magic;                  //校验AutoreleasePagePoolPage结构是否完整
    id *next;                             //指向新加入的autorelease对象的下一个位置,初始化时指向begin()
    pthread_t const thread;               //当前所在线程,AutoreleasePool是和线程一一对应的
    AutoreleasePoolPage * const parent;   //指向父节点page,第一个结点的parent值为nil
    AutoreleasePoolPage *child;           //指向子节点page,最后一个结点的child值为nil
    uint32_t const depth;                 //链表深度,节点个数
    uint32_t hiwat;                       //数据容纳的一个上限
    //......
};

每个自动释放池都是是由若干个 AutoreleasePoolPage组成的双向链表结构,如下图所示:
在这里插入图片描述

AutoreleasePoolPage中拥有 parent和 child指针,分别指向上一个和下一个 page;当前一个 page的空间被占满(每个 AutorelePoolPage的大小为4096字节)时,就会新建一个 AutorelePoolPage对象并连接到链表中,后来的 Autorelease对象也会添加到新的 page中;

另外,当 next==begin()时,表示 AutoreleasePoolPage为空;
当 next ==end(),表示 AutoreleasePoolPage已满。

AutoreleasePool子线程上的释放时机

子线程默认不开启 RunLoop,那么其中的延时对象该如何释放呢?其实这依然要从 Thread和 AutoreleasePool的关系来考虑:

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects.

也就是说,每一个线程都会维护自己的 Autoreleasepool栈,所以子线程虽然默认没有开启 RunLoop,但是依然存在 AutoreleasePool,在子线程退出的时候会去释放 autorelease对象。
前面讲到过,ARC会根据一些情况进行优化,添加 __autoreleasing修饰符,其实这就相当于对需要延时释放的对象调用了 autorelease方法。从源码分析的角度来看,如果子线程中没有创建 AutoreleasePool ,而一旦产生了 Autorelease对象,就会调用 autoreleaseNoPage方法自动创建 hotpage,并将对象加入到其栈中。所以,一般情况下,子线程中即使我们不手动添加自动释放池,也不会产生内存泄漏。

weak修饰符补充

在前面的博客中,我们知道了weak指针的一个生命周期是objc_initWeak函数到objc_destroyWeak函数,但其实这两个函数中都引用了另一个runtime的函数storeWeak,这个函数和我们刚刚见到的storeStrong函数是对应的,现在我们到NSObject.mm中看看他们的源码:

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

storeWeak:

static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew); // 断言必须有旧值或新值
    if (!haveNew) ASSERT(newObj == nil); // 如果没有新值,断言新对象为 nil

    Class previouslyInitializedClass = nil; // 之前初始化的类
    id oldObj; // 旧对象
    SideTable *oldTable; // 旧对象的 SideTable
    SideTable *newTable; // 新对象的 SideTable

    // 获取旧值和新值的锁。按锁地址排序以防止锁排序问题。
    // 如果旧值在操作过程中发生变化,重试。
 retry:
    if (haveOld) {
        oldObj = *location; // 获取旧对象
        oldTable = &SideTables()[oldObj]; // 获取旧对象的 SideTable
    } else {
        oldTable = nil; // 没有旧值
    }
    if (haveNew) {
        newTable = &SideTables()[newObj]; // 获取新对象的 SideTable
    } else {
        newTable = nil; // 没有新值
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); // 获取旧值和新值的锁

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 如果旧值在操作过程中发生变化,解锁并重试
        goto retry;
    }

    // 为了防止弱引用机制与 +initialize 机制之间的死锁,确保没有弱引用的对象具有未初始化的 isa。
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa(); // 获取新对象的类
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) // 如果类未初始化
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 解锁
            class_initialize(cls, (id)newObj); // 初始化类

            previouslyInitializedClass = cls; // 更新之前初始化的类

            goto retry; // 重试
        }
    }

    // 清理旧值(如果有)。
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 从弱引用表中解除登记旧对象
    }

    // 登记新值(如果有)。
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating); // 在弱引用表中登记新对象
        // weak_register_no_lock 如果弱存储应被拒绝,则返回 nil

        // 在引用计数表中设置 is-weakly-referenced 位。
        if (!_objc_isTaggedPointerOrNil(newObj)) {
            newObj->setWeaklyReferenced_nolock(); // 设置新对象为弱引用
        }

        // 不要在其他地方设置 *location。这会引入竞争条件。
        *location = (id)newObj; // 更新位置指针为新对象
    }
    else {
        // 没有新值。存储没有变化。
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 解锁旧值和新值的 SideTable

    // 这个操作必须在解锁后调用,因为它可能会调用任意代码。
    // 特别是,即使 _setWeaklyReferenced 没有实现,resolveInstanceMethod: 可能实现,并可能回调到弱引用机制。
    callSetWeaklyReferenced((id)newObj); // 调用设置弱引用的方法

    return (id)newObj; // 返回新对象
}

这段代码的主要思路是管理弱引用对象,首先获取旧对象和新对象并锁定相关表以防止多线程冲突,接着检查并确保新对象的类已初始化,然后从弱引用表中解除旧对象的登记,再将新对象登记到弱引用表并设置弱引用标志,最后解锁并调用设置弱引用的方法,从而安全地更新和管理弱引用对象。

弱引用表是Objective-C中用来管理和追踪弱引用对象的数据结构。在Objective-C的内存管理中,弱引用是一种不会增加对象引用计数的引用类型,这意味着当对象没有强引用时,弱引用不会阻止对象被释放。
弱引用表的主要作用包括三个方面:首先,它能够跟踪记录被弱引用的对象,并存储这些弱引用的位置;其次,当对象的所有强引用都被释放后,系统会自动将这些对象从弱引用表中移除,并将弱引用设置为nil,从而防止悬挂指针问题的发生;最后,通过弱引用表,可以有效地防止因循环引用而导致的内存泄漏,因为弱引用不会增加对象的引用计数,也不会导致对象无法释放。

ARC不会优化的场景。

ARC适用于绝大多数场景,但并不是万能的,例如performSelect系列有许多方法,带有选择子,编译器不知道选择子具体是什么,必须到了运行期才能确定,因此在编译时,不知道其方法名,没法利用ARC内存规则来判断是否释放。

如果这时我们使用selector(newTest)就会造成内存泄漏,因为ARC此时不会帮助我们添加release。

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

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

相关文章

SQL注入问题

一、什么是sql注入 public class TestSql {public static void main(String[] args) {Scanner inScanner new Scanner(System.in);System.out.println("请输入用户名");String username inScanner.nextLine();System.out.println("请输入密码");String …

php基础: 三角形

包含&#xff1a;左三角、左上三角、右三角、右上三角、等腰三角、倒等腰三角。注意空格的数量&#xff0c;因为*号后面加了空格 /*** * 左三角形* param $n* return void*/ function triangleLeft($n){echo <pre>;for ($i 1; $i < $n; $i) {for ($j 1; $j < $i…

MongoDB常用命令大全

文章目录 一、MongoDB简介二、服务启动停止备份三、数据库相关四、集合操作五、文档操作六、其他常用命令 一、MongoDB简介 MongoDB是一款流行的NoSQL数据库&#xff0c;以其灵活的文档模型、高可用性、易于扩展等特性而受到广泛关注。 MongoDB 是由C语言编写的&#xff0c;是…

C# modbus 图表

控件&#xff1a;chart1(图表)&#xff0c;cartesianChart1(第三方添加图表)&#xff0c;timer(时间) 添加第三方&#xff1a; 效果&#xff1a;图标会根据连接的温度&#xff0c;湿度用timer时间进行改变 Chart1控件样式&#xff1a;Series添加线条&#xff0c;颜色&#xf…

劳易测应用案例 汽车零部件装配线光电传感器解决方案

汽车零部件种类繁多&#xff0c;形状、尺寸、功能各异&#xff0c;生产线的规划与布局必须紧密贴合产品的独特工艺、精细装配流程及高效生产需求。随着电动汽车时代的到来&#xff0c;生产标准愈加严格&#xff0c;对生产线的设计和装配周期提出了更高要求。市场要求生产线不仅…

EE trade:强平和爆仓的区别

在金融交易市场中&#xff0c;杠杆交易的引入&#xff0c;让投资者可以用少量的资金撬动更大的头寸&#xff0c;获取更大的收益。然而&#xff0c;杠杆交易也带来了更大的风险&#xff0c;一旦市场波动&#xff0c;投资者可能会面临强平或爆仓的风险。了解强平和爆仓的区别&…

MySQL-对数据库和表的DDL命令

文章目录 一、什么是DDL操作二、数据库编码集和数据库校验集三、使用步骤对数据库的增删查改1.创建数据库2.进入数据库3.显示数据库4.修改数据库mysqldump 5.删除数据库 对表的增删查改1.添加/创建表2.插入表内容3.查看表查看所有表查看表结构查看表内容 4.修改表修改表的名字修…

保障低压设备安全!中国星坤连接器精密工艺解析!

在现代电子设备中&#xff0c;连接器扮演着至关重要的角色&#xff0c;它们是电子系统之间沟通的桥梁。随着技术的发展&#xff0c;对连接器的需求也在不断提升&#xff0c;特别是在低电压应用领域。中国星坤最新推出的低压连接器&#xff0c;以其精密性和安全性&#xff0c;为…

Msql数据库之DDL(数据定义语言)的相关操作

数据定义语言(DDL)&#xff1a;用于创建、修改和删除数据库对象&#xff0c;如数据库、表、视图、索引等 一、数据库的相关操作&#xff1a; 1、创建数据库 语法&#xff1a;create database [if not exists ] 数据库名; 例&#xff1a;create database if not exists test…

2024-07-16 Unity插件 Odin Inspector6 —— Group Attributes

文章目录 1 说明2 Group 特性2.1 BoxGroup2.2 ButtonGroup2.3 FoldoutGroup2.4 ShowIfGroup / HideIfGroup2.5 HorizontalGroup2.6 ResponsiveButtonGroup2.7 TabGroup2.8 ToggleGroup2.9 VerticalGroup 1 说明 ​ 本文介绍 Odin Inspector 插件中有关 Group 特性的使用方法。…

【Apache POI】Java解析Excel文件并处理合并单元格-粘贴即用

同为牛马&#xff0c;点个赞吧&#xff01; 一、Excel文件样例 二、工具类源码 import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.usermodel.XSSFWorkbookFactory; import org.springframework.web.multip…

【B树、B-树、B+、B*树】

目录 一、B-树&#xff08;即B树&#xff09;的定义及操作1.1、定义1.2、操作1.2.1、查找1.2.2、插入1.2.3、删除 二、B树的定义及操作2.1、定义2.2、操作2.2.1、查找2.2.2、插入2.2.3、删除 三、B*树 一、B-树&#xff08;即B树&#xff09;的定义及操作 1.1、定义 B-tree即…

解决vue3中el-input在form表单按下回车刷新页面

问题&#xff1a;在input框中点击回车之后不是调用我写的回车事件&#xff0c;而是刷新页面 原因&#xff1a; 如果表单中只有一个input 框则按下回车会直接关闭表单 所以导致刷新页面 解决方法 &#xff1a; 再写一个input 表单 &#xff0c;并设置style"display:none&…

【对顶堆 优先队列】2102. 序列顺序查询

本文涉及知识点 对顶堆 优先队列 LeetCode 2102. 序列顺序查询 一个观光景点由它的名字 name 和景点评分 score 组成&#xff0c;其中 name 是所有观光景点中 唯一 的字符串&#xff0c;score 是一个整数。景点按照最好到最坏排序。景点评分 越高 &#xff0c;这个景点越好。…

再谈有关JVM中的四种引用

1.强引用 强引用就是我们平时使用最多的那种引用&#xff0c;就比如以下的代码 //创建一个对象 Object obj new Object();//强引用 这个例子就是创建了一个对象并建立了强引用&#xff0c;强引用一般就是默认支持的当内存不足的时候&#xff0c;JVM开始垃圾回收&#xff0c…

【Java--数据结构】二叉树oj题(上)

前言 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 判断是否是相同的树 oj链接 要判断树是否一样&#xff0c;要满足3个条件 根的 结构 和 值 一样左子树的结构和值一样右子树的结构和值一样 所以就可以总结以下思路…

js补环境系列之剖析:原型、原型对象、实例对象三者互相转化(不讲废话、全是干货)

【作者主页】&#xff1a;小鱼神1024 【擅长领域】&#xff1a;JS逆向、小程序逆向、AST还原、验证码突防、Python开发、浏览器插件开发、React前端开发、NestJS后端开发等等 思考下&#xff1a;js补环境中&#xff0c;什么场景会用到原型、原型对象、实例对象&#xff1f; 举…

最大文件句柄数

优质博文&#xff1a;IT-BLOG-CN 灵感来源 一、什么是文件句柄 文件句柄File Handle是操作系统中用于访问文件的一种数据结构&#xff0c;通常是一个整数或指针。文件句柄用于标识打开的文件&#xff0c;每个打开的文件都有一个唯一的文件句柄。 它们是对文件、网络套接字或…

商业数据分析思维的培训PTT制作大纲分享

商业数据分析思维的培训PTT制作大纲: 基本步骤: 明确PPT的目的和主题 收集并整理相关内容资料 构思并确定PPT的框架大纲 编写PPT的内容文字 插入图片、图表等视觉元素 设计PPT的版式和模板 排练并修改PPT 输出并备份最终版本 目的:数据思维培养; 主题:商业数据分…

【TensorRT】Yolov5-DeepSORT 目标跟踪

Yolov5-DeepSORT-TensorRT 本项目是 Yolo-DeepSORT 的 C 实现&#xff0c;使用 TensorRT 进行推理 &#x1f680;&#x1f680;&#x1f680; 开源地址&#xff1a;Yolov5_DeepSORT_TensorRT&#xff0c;求 star⭐ ~ 引言 ⚡ 推理速度可达25-30FPS&#xff0c;可以落地部署&…