【iOS】copystrong原理+深浅拷贝+完全拷贝

news2024/12/22 19:57:54

Copy&Strong原理

Copy探究

在回答copy的各种问题前,我们需要先了解我们为什么要使用copy。

    1. 拷贝的目的 : 产生一个副本对象,跟源对象互不影响
    • 修改了源对象,不会影响副本对象
    • 修改了副本对象,不会影响源对象
    1. iOS提供了2个拷贝方法
      1. copy,不可变拷贝,产生不可变副本
      1. mutableCopy, 可变拷贝,产生可变副本
    1. 深拷贝和浅拷贝
      1. 深拷贝 : 内容拷贝,产生新的对象
      1. 浅拷贝 : 指针拷贝, 没有产生新的对象

下面是是6个test,分别是 可变/不可变字符串/数组/字典 对应的 copy和mutableCopy操作。
复习的时候都不用看,直接看结论,想清楚都很简单

test1

对不可变字符串进行copy&mutableCopy操作


void test1()
{
	NSString *str1 = [NSString stringWithFormat:@"test"];
	NSString *str2 = [str1 copy]; // 返回的是NSString
	NSMutableString *str3 = [str1 mutableCopy]; // 返回的是NSMutableString
	NSLog(@"%p %p %p", str1, str2, str3);
}

在这里插入图片描述

我们根据打印的地址可以看出不可变字符串在copy时是浅拷贝,只拷贝了指针没有拷贝对象;mutableCopy则是深拷贝,产生了新的对象

test2

对可变字符串进行copy&mutableCopy操作


void test2()
{
	NSMutableString *str1 = [[NSMutableString alloc] initWithFormat:@"test"]; // 1
	NSString *str2 = [str1 copy]; // 深拷贝
	NSMutableString *str3 = [str1 mutableCopy]; // 深拷贝
	NSLog(@"%p %p %p", str1, str2, str3);
}

在这里插入图片描述

我们根据打印的地址可以看出对于可变字符串不论是copy还是mutableCopy都是深拷贝

test3

对不可变数组进行copy&mutableCopy操作


void test3()
{
	NSArray *array1 = [[NSArray alloc] initWithObjects:@"a", @"b", nil];
	NSArray *array2 = [array1 copy]; // 浅拷贝
	NSMutableArray *array3 = [array1 mutableCopy]; // 深拷贝

	NSLog(@"%p %p %p", array1, array2, array3);
}

在这里插入图片描述

我们根据打印的地址可以看出不可变数组在copy时是浅拷贝,只拷贝了指针没有拷贝对象;mutableCopy则是深拷贝,产生了新的对象

test4

对可变数组进行copy&mutableCopy操作


void test4()
{
	NSMutableArray *array1 = [[NSMutableArray alloc] initWithObjects:@"a", @"b", nil];
	NSArray *array2 = [array1 copy]; // 深拷贝
	NSMutableArray *array3 = [array1 mutableCopy]; // 深拷贝

	NSLog(@"%p %p %p", array1, array2, array3);
}

在这里插入图片描述

我们根据打印的地址可以看出对于可变数组不论是copy还是mutableCopy都是深拷贝

test5

对不可变字典进行copy&mutableCopy操作


void test5()
{
	NSDictionary *dict1 = [[NSDictionary alloc] initWithObjectsAndKeys:@"jack", @"name", nil];
	NSDictionary *dict2 = [dict1 copy]; // 浅拷贝
	NSMutableDictionary *dict3 = [dict1 mutableCopy]; // 深拷贝

	NSLog(@"%p %p %p", dict1, dict2, dict3);
}

在这里插入图片描述

我们根据打印的地址可以看出不可变字典在copy时是浅拷贝,只拷贝了指针没有拷贝对象;mutableCopy则是深拷贝,产生了新的对象

test6

对可变字典进行copy&mutableCopy


void test6()
{
	NSMutableDictionary *dict1 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"jack", @"name", nil];
	NSDictionary *dict2 = [dict1 copy]; // 深拷贝
	NSMutableDictionary *dict3 = [dict1 mutableCopy]; // 深拷贝

	NSLog(@"%p %p %p", dict1, dict2, dict3);

}

在这里插入图片描述

结论

在这里插入图片描述

只要记住copy的目的是为了,修改拷贝后的数据不影响拷贝前的数据,修改拷贝前的数据不影响拷贝后的数据,结论都可以退出来

  1. 三个容器的结论都是一样的
  2. 只有不可变容器使用不可变拷贝(copy)时,才会浅拷贝。 因为只有这样,修改数据才不会相互影响
  3. 除了上述情况,其他情况都是深拷贝

让自己的类用 copy 修饰符

我们copy属性一般只对NSString NSArray NSDictionary NSSet等这些可用,假如我们要对我们的类对象进行copy实现,我们应该怎么做呢?

若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。

copy&strong实现原理

两者的区别

  • copy:用该修饰符进行修饰的属性,在调用该属性的set方法时,会进行一次copy操作,新开辟一块内存区域,所以原对象指针指向的内存区域值被修改不会造成影响.因为做了一次深拷贝.
  • strong:用该修饰符进行修饰的属性,引用计数+1,但仅仅拷贝了指针,实际跟原指针指向同一块内存区域,所以原指针指向的内存区域值被修改之后,新的对象值也会被修改.这里仅仅是浅拷贝.

copy内部做了什么处理

现在来看下到底copy到底是做了什么,才能保证值是不可变的.
这里我们通过clang的编译命令将oc文件编译成底层的c++源码一探究竟.通过命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m将对应的.m文件编译成c++文件xxx.cpp,由于编译后会产生很多系统的代码,这里我们只贴上我们自己写的代码:

  static void _I_ViewController_testStringCopy(ViewController * self, SEL _cmd) {

    NSMutableString *mut = ((NSMutableString * _Nonnull (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("stringWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_50__967f2m57k5dkqm4fm300hwr0000gn_T_ViewController_574830_mi_3);
    
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setCopyedStr:"), (NSString *)mut);
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setStrongStr:"), (NSString *)mut);
    
    ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)mut, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_50__967f2m57k5dkqm4fm300hwr0000gn_T_ViewController_574830_mi_4);
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_50__967f2m57k5dkqm4fm300hwr0000gn_T_ViewController_574830_mi_5, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("copyedStr")));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_50__967f2m57k5dkqm4fm300hwr0000gn_T_ViewController_574830_mi_6, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("strongStr")));

}

static NSString * _I_ViewController_copyedStr(ViewController * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_ViewController$_copyedStr)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_ViewController_setCopyedStr_(ViewController * self, SEL _cmd, NSString *copyedStr) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ViewController, _copyedStr), (id)copyedStr, 0, 1); }

static NSString * _I_ViewController_strongStr(ViewController * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_ViewController$_strongStr)); }
static void _I_ViewController_setStrongStr_(ViewController * self, SEL _cmd, NSString *strongStr) { (*(NSString **)((char *)self + OBJC_IVAR_$_ViewController$_strongStr)) = strongStr; }

这里注意下这两个属性的set方法的实现

static void _I_ViewController_setCopyedStr_(ViewController * self, SEL _cmd, NSString *copyedStr) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ViewController, _copyedStr), (id)copyedStr, 0, 1); }

static void _I_ViewController_setStrongStr_(ViewController * self, SEL _cmd, NSString *strongStr) { (*(NSString **)((char *)self + OBJC_IVAR_$_ViewController$_strongStr)) = strongStr; }

这里可以看出使用copy修饰的属性跟strong修饰的属性底层的set实现是不同的,strong的实现就是根据地址偏移找到对应的实例变量,然后直接赋值,所以当对象的指针指向的内存区域被修改之后,属性值也会同步被修改。

而使用copy修饰的其set方法内部调用了extern “C” __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);这个函数,第一个参数为属性的实例对象,第二个参数为SEL方法,第三个参数offset,是相对于self的偏移,用来找对应的成员变量的,第四个是新值,第五个是是否为原子性atomic,最后一个为是否需要进行copy操作。

通过clang编译出来的源码目前只能看到这个程度,我们需要去苹果OC源码地址(将oc的源码下载下来,打开工程去查看对应的函数实现)去查看objc_setProperty这个函数的具体实现才能知道内部做了什么操作。

通过查看源码,objc_setProperty具体实现如下

   void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

在上面编译出来的c++源码中能看到系统调用这个函数的时候, shouldCopy设置的是1(最后一个参数)

static void _I_ViewController_setCopyedStr_(ViewController * self, SEL _cmd, NSString *copyedStr) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ViewController, _copyedStr), (id)copyedStr, 0, 1); }

所以这里告诉系统,这个对象需要进行拷贝。

bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);

这里是有两个copy标识的,当shouldCopy=1,并且不是mutable copy时需要copy操作,如果shouldCopy为mutable copy时,进行mutable copy操作(这里的MUTABLE_COPY就是个宏定义#define MUTABLE_COPY 2)。

接下来看实际内部真正实现的函数reallySetProperty:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

当copy或者mutableCopy为true时会调用copyWithZone/mutableCopyWithZone对属性进行一层深拷贝,这里就可以解释为什么用copy为什么原属性不会被修改,因为内存地址不是同一块了.而copy修饰的属性copy为true,mutableCopy为flase,所以只要是被copy修饰的属性,不管传入的值是否是可变的,最终赋值给属性后都是不可变的,因为这里调用了copyWithZone方法。

总结

所以具体NSString还有集合(NSArray、NSDictionary、NSSet)对象是否需要使用copy来修饰,完全可以根据情况来设置,如果这个值可以100%保证不会被修改的,那么使用strong即可,如果存在被修改的风险的则优先考虑使用copy来修饰。

使用copy修饰符会增加部分性能的消耗,从源码实现可以看出,copy修饰的对象底层实现的时候都需要进行判断,是否需要copy操作,如果项目过于庞大,copy修饰符使用过多也会造成性能消耗,所以如果这个值不存在被修改的风险,建议用strong优化性能

容器的里面的内容的深浅拷贝

使用copy、mutableCopy对集合对象进行的深浅拷贝是针对集合对象本身的

使用 copy、mutableCopy 对集合对象(NSArray、NSDictionary、NSSet)进行的深浅拷贝是针对集合对象本身的,对集合中的对象执行的默认都是浅拷贝。也就是说只拷贝集合对象本身,而不复制其中的数据。主要原因是,集合内的对象未必都能拷贝,而且调用者也未必想在拷贝集合时一并拷贝其中的每个对象。

如果想要深拷贝集合对象本身的同时,对其元素也进行copy操作,可使用一下方法:

NSArray* deepCopyArray = [[NSArray alloc] initWithArray: someArray copyItems: YES];

这样集合中的每一个对象都会收到copyWithZone:消息,需要注意的是集合中的对象都必须符合NSCopying 协议,否则会导致程序崩溃或中断
copyWithZone:是一个用于实现对象复制的方法,当被调用时,方法内部会先创建一个新实例,再将当前对象self的属性值复制到新实例中,最后返回新实例

注意:
initWithArray: copyItems: 方法不是所有情况下都深拷贝集合对象本身的。如果执行[[NSArray alloc] initWithArray: @[] copyItems: aBoolValue];,也就是原对象为不可变的空数组的话,对原对象本身执行的是浅拷贝,苹果对 @[] 使用了享元

享元是一种设计模式,详情见这篇博客:享元模式

那么如何实现真正的深拷贝(完全拷贝)呢?

先说结论:先归档后解档

从上面的叙述可知,如果集合中的对象的copy操作是浅拷贝,那么对于集合来说还不是真正意义上的拷贝。比如,现需要对一个NSArray<NSArray *>对象进行真正的深拷贝,那么内层数组及其内容元素也应该执行深拷贝,可以对该集合对象进行归档然后解档,只要集合中的对象都符合 NSCoding 协议。而且,使用这种方式,无论集合中存储的模型对象嵌套多少层,都可以实现深拷贝,但前提是嵌套的子模型也需要符合 NSCoding 协议才行,否则也会导致程序崩溃或中断

以下为真正的深拷贝:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData: [NSKeyedArchiver archivedDataWithRootObject: oldArray]];

在这段代码中,oldArray被传递给archivedDataWithRootObject:方法,并返回一个包含该oldArray对象及其关联数据的NSData对象,通过将前一步得到的NSData对象传递给unarchiveObjectWithData:方法,我们得到了一个解档后的对象,即trueDeepCopyArray

通过这个过程,我们实现了oldArray的深拷贝,得到了一个内容与原始数组相同的新数组trueDeepCopyArrayoldArray及其所有的元素必须符合 NSCoding 协议。如果其中的某个元素不支持归档,则可能会导致错误

在这里插入图片描述

就原对象和副本对象的可变性而言

使用initWithArray: copyItems: YES时,生成的副本集合中的对象(下一个级别)是不可变的,而更深层级的对象仍然保持其之前的可变性。换句话说,副本集合的第一级别是不可变的,但更深层级的对象仍然可以进行修改

来看这段代码

NSArray* oldArray = @[@[].mutableCopy];
NSArray* deepCopyArray = [[NSArray alloc] initWithArray: oldArray copyItems: YES];
NSMutableArray* mArray = deepCopyArray[0]; // deepCopyArray[0] 已经被深拷贝为 NSArray (不可变)对象
[mArray addObject:@""];

首先创建了一个空的NSArray实例[],并调用mutableCopy方法创建该数组的可变副本,即一个NSMutableArray实例,最后放进oldArray中。用此方法深拷贝后,deepCopyArray[0]已然不可变。因此给mArray添加对象时程序会报错

归档解档集合的方式可以保留集合对象所有级别的可变性,就像在归档之前一样。通过归档解档,可以以完整的形式将可变集合对象存储到磁盘上,并在需要时重新加载,保留了集合对象层次结构及其可变性

实现对自定义对象的拷贝

实现对自定义对象的拷贝,需要遵守NSCopying 协议,并实现copyWithZone:方法。

  • 如果要浅拷贝,copyWithZone: 方法就返回当前对象:return self;
- (id)copyWithZone:(NSZone *)zone {
    return self;
}

如果要深拷贝,copyWithZone: 方法中就创建新对象,并给希望拷贝的属性赋值,然后将其返回。如果有嵌套的子模型也需要深拷贝,那么子模型也需符合NSCopying 协议且在属性赋值时调用子模型的copy方法,以此类推


@interface Dog : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@end

@implementation Dog
- (id)copyWithZone:(NSZone *)zone {
    Dog *copy = [[Dog alloc] init];
    copy.name = self.name;
    return copy;
}
@end

@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) Dog *dog;
@end

@implementation Person
- (id)copyWithZone:(NSZone *)zone {
    Person *copy = [[Person alloc] init];
    copy.name = self.name;
    copy.age = self.age;
    copy.dog = [self.dog copy]; //调用了嵌套子模型Dog的copy方法进行深拷贝
    return copy;
}
@end

如果自定义对象支持可变拷贝和不可变拷贝,那么还需要遵守NSMutableCopying 协议,并实现mutableCopyWithZone:方法,返回可变副本。而copyWithZone:方法返回不可变副本。我们可根据需要调用该对象的 copy mutableCopy 方法来进行不可变拷贝可变拷贝


@interface Person : NSObject <NSMutableCopying, NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person
- (id)mutableCopyWithZone:(NSZone *)zone {
    Person *copy = [[Person alloc] init];
    copy.name = [self.name mutableCopy]; //使用可变拷贝的方式来生成可变副本
    copy.age = self.age;
    return copy;
}

- (id)copyWithZone:(NSZone *)zone {
    Person *copy = [[Person alloc] init];
    copy.name = self.name; //使用不可变拷贝的方式来生成不可变副本
    copy.age = self.age;
    return copy;
}
@end

面试题

1. 请简单介绍一下iOS的浅拷贝和深拷贝?

答:

  • 浅拷贝:指针的拷贝,实际上拷贝了指向原来对象的指针,不会创建新的对象!
  • 深拷贝:拷贝对象的具体内容,创建新的对象。拷贝结束后,两个对象存的值是相同的,但是内存地址不一样!(至少有一层是深拷贝)

2. 请问NSString属性用什么修饰符修饰,为什么用这个修饰符修饰?

答:
开发中NSString属性用copy修饰符修饰。
之所以用copy来修饰NSString属性,是因为可以避免赋值来源是NSMutableString的时候,改变可变字符串的值会影响不可变字符串的值。之所以用copy后就不会影响其原有值,是因为用copy修饰不可变字符串,在赋值的时候,在setter方法中会对赋值来源执行一次copy操作,当对可变类型执行copy操作,返回新的不可变对象,即使改变了来源数据,也不会影响copy修饰的不可变属性!所以用copy修饰NSString属性,也可以保证数据安全,不受污染!

3. 你了解iOS中的完全拷贝吗?实现方法?

答:
完全拷贝的概念是相对于深拷贝来说的。在深拷贝的理解中有一种说法是至少是一层深拷贝,比如,我们对容器类型的对象执行一次mutableCopy操作时,就进行了深拷贝,但这次深拷贝是单层深拷贝,也就是说容器内存地址发生了变化,但容器内元素的内存地址没有发生变化,我们称它为单层深拷贝,并不是理论上的完全深拷贝!(容器类型的内容拷贝,仅限于对象本身,对象元素仍然是指针拷贝)

实现方法:

    1. 使用以下方法: 且集合中的对象都必须符合NSCopying 协议
      NSArray* deepCopyArray = [[NSArray alloc] initWithArray: someArray copyItems: YES];
    1. 先归档后解档

4.如何实现自定义类的深浅拷贝?

系统提供的一些类,比如NSString,NSArray,NSDictionry都遵循了NSCopying和NSMutableCopying协议,所以我们可以直接调用copy/mutableCopy方法。我们自定义的类也想要实现深浅拷贝的功能,我们自定义的类也需要遵循NSCopying和NSMutableCopying协议,并实现对应的协议方法!

自定义的类实现浅拷贝:遵循NSCopying协议,实现copyWithZone方法,方法内返回self!

自定义的类实现深拷贝:遵循NSMutableCopying协议,实现mutableCopyWithZone方法,方法内创建新对象,并把当前类的属性依次赋值给新对象的属性,最后返回这个新的对象!

实现自定义A类的深拷贝的时候,如果A类中嵌套B类,那么B类也要实现NSMutableCopying协议,现mutableCopyWithZone方法,方法内创建新对象,并把当前类的属性依次赋值给新对象的属性,最后返回这个新的B对象!并且,在A类的mutableCopyWithZone方法中, newA.b = [self.b mutableCopy] 。

这种实现方式只针对简单的单层的类!

上述是基本的实现方式!
下面介绍一种使用Runtime+KVC方式给新对象进行赋值!参考代码实现!(这只是浅拷贝,面试别说了)


CustomModel *m1 = [CustomModel new];
m1.name = @"Shaw";
m1.age = 27;
CustomModel *m2 = [m1 copy];
m2.name = @"CTT";
m2.age = 28;
    
NSLog(@"%@&%@", m1.name, m2.name);
// 打印结果:Shaw&CTT
 
@interface CustomModel : NSObject <NSCopying>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@end
 
@implementation CustomModel
 
- (id)copyWithZone:(NSZone *)zone {
    CustomModel *copy = [[[self class] alloc] init];
    unsigned int propertyCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([self class], &propertyCount);
    for (int i = 0; i < propertyCount; i++ ) {
        objc_property_t thisProperty = propertyList[i];
        const char* propertyCName = property_getName(thisProperty);
        NSString *propertyName = [NSString stringWithCString:propertyCName encoding:NSUTF8StringEncoding];
        id value = [self valueForKey:propertyName];
        [copy setValue:value forKey:propertyName];
    }
    return copy;
}
// 注意此处需要实现这个函数,因为在通过Runtime获取属性列表时,会获取到一个名字为hash的属性名,这个是系统帮你生成的一个属性
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {}

代码实现解释:

  • KVC:通过 valueForKey: 和 setValue:forKey: 轻松地获取和设置对象的属性值,而无需显式调用 getter 或 setter 方法。
  • Runtime:通过 class_copyPropertyList 动态获取对象的属性列表,使得 copyWithZone: 方法可以自动适应类中的所有属性,即使属性列表发生变化也不需要手动修改代码。
  • setValue:forUndefinedKey: 的空实现保证了 KVC 操作的安全性,避免未定义键导致的崩溃。

5.如果自定义的类有多层嵌套,有没有方法实现自定义类的深拷贝?

答:
解档和归档

  • 1 . 自定义类包括嵌套的类都要实现NSCoding协议,并实现encodeWithCoder/initWithCoder协议方法,

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

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

相关文章

量化方法怎么选?如何评估量化后的大模型LLM?

文章内容总结自&#xff1a;Evaluating Quantized Large Language Models&#xff08;https://arxiv.org/abs/2402.18158&#xff09; 如果想深入了解量化的基本概念和如何用代码实现请参考&#xff1a; Michael&#xff1a;用python代码深入浅出量化概念 &#xff08;https…

YOLOv10改进 | 注意力篇 | YOLOv10引入SpatialGroupEnhance注意力机制,并构建C2f_SGE

1. SGE介绍 1.1 摘要:卷积神经网络(CNN)通过收集语义子特征的层次和不同部分来生成复杂对象的特征表示。这些子特征通常可以以分组的形式分布在每层的特征向量中[43,32],代表各种语义实体。然而,这些子特征的激活通常在空间上受到相似模式和噪声背景的影响,从而导致错误…

虚拟机win server安装配置DNS服务器

准备工作 创建DNS服务器前将自己的网卡地址设置为静态IP&#xff0c;选择自己的网卡。因为本机作为DNS服务器所以将DNS服务器地址设为了自己的回环地址。 一、使用服务器管理安装 DNS 服务器 1、登录服务器后&#xff0c;服务器会默认启用“服务器管理”页面&#xff0c;选择…

提前批测开三面,已OC!

大家好&#xff0c;我是洋子 近期百度提前批已经开始有一段时间了&#xff0c;甚至已经有不少 25 届的同学 oc 了&#xff0c;这里分享一位已经顺利 oc 百度提前批测开岗位同学的三轮面试面经 整个三轮技术面试总体难度不高&#xff0c;但考察知识广度比较广&#xff0c;如果…

搭建 STM32 网关服务器的全流程:集成嵌入式 C++、TCP/IP 通信、Flash 存储及 JWT 认证(含代码示例)

引言 随着物联网&#xff08;IoT&#xff09;技术的快速发展&#xff0c;基于 STM32 的服务器&#xff08;类似网关&#xff09;在数据采集、设备控制等方面的应用越来越广泛。本文将介绍搭建一个基于 STM32 的服务器所需的技术栈&#xff0c;以及详细的搭建步骤和代码示例。 …

好的养宠空气净化器是智商税吗?好的养宠空气净化器用户体验

家里养了两只“超级掉毛怪”,家里的猫毛满天飞&#xff0c;衣服床餐具等到处都是&#xff01;感受一下40度高温的养猫人&#xff0c;给掉毛怪疏毛浮毛飘飘&#xff0c;逃不过的饮水机&#xff0c;各个角落&#xff0c;多猫拉臭传来的异味。 一、养猫需要解决的问题 掉毛&#…

Tsan-ThreadSanitizer之As if synchronized via sleep

最近在调试ffmpeg的时候&#xff0c;加入了tsan&#xff0c;结果出现了下面提示&#xff1a; 具体什么意思呢&#xff0c;找了很久找到了官方介绍&#xff1a; https://github.com/google/sanitizers/wiki/ThreadSanitizerReportFormat

大数据-61 Kafka 高级特性 消息消费02-主题与分区 自定义反序列化 拦截器 位移提交 位移管理 重平衡

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Git的一些简单使用

下列内容适用于git初学者&#xff0c;从创建本地git仓库到提交的一个基本过程1. 1.创建git仓库 在想创建git仓库的路径下打开git bash&#xff0c;输入以下命令行创建仓库&#xff08;一般来说&#xff0c;我觉得直接在code workspace得地方创建git仓库就可以了&#xff0c;这…

acme.sh生成https证书

前言 SSL 价格并不便宜, 本节介绍如何使用 acme.sh 生成免费的 SSL 证书 证书生成原理 CA && Let’s Encrypt 证书颁发机构&#xff08;CA&#xff0c;Certificate Authority&#xff09;是一个负责颁发数字证书的实体。数字证书用于在互联网上验证实体的身份&…

注册或购买的谷歌账号的辅助邮箱是否需要设置?有什么用?设置的要点是什么?

今天早上&#xff0c;有个朋友联系到GG账号服务&#xff0c;问我谷歌账号辅助邮箱怎么用。说实在的这个问题有点抽象&#xff0c;哈哈。 然后我详细了解了一下&#xff0c;原来是这样的&#xff1a; 他的谷歌账号提示异常&#xff08;这个时候账号肯定是被停用了的&#xff09…

【Linux应用编程】Day12线程

线程 与进程类似&#xff0c;线程是允许应用程序并发执行多个任务的一种机制&#xff0c;线程参与系统调度&#xff1b; 事实上&#xff0c;系统调度的最小单元是线程、而并非进程。 ⚫ 线程的基本概念&#xff0c;线程 VS 进程&#xff1b; ⚫ 线程标识&#xff1b; ⚫ 线…

电脑上有什么好用的记笔记软件吗?试试这3款笔记软件,功能丰富又实用

笔记软件千千万&#xff0c;日常使用方便最关键&#xff01;&#xff01; 推荐3个各有亮点的笔记软件&#xff0c;不止是记笔记这么简单&#xff1a; 1、FlowUs 推荐指数&#xff1a;☆☆☆☆☆ 关键词&#xff1a;文档笔记软件 下载链接>>flowus.cn FlowUs是一款在…

ADI - 通过5 V至24 V输入提供双极性、双向DC-DC流入和流出电流

大部分电子系统都依赖于正电压轨或负电压轨&#xff0c;但是有些应用要求单电压轨同时为正负电压轨。在这种情况下&#xff0c;正电源或负电源由同一端子提供&#xff0c;也就是说&#xff0c;电源的输出电压可以在整个电压范围内调节&#xff0c;并且可以平稳转换极性。例如&a…

【mars3d】实现线面内插值计算效果

面插值计算效果展示&#xff1a; &#xff08;离屏渲染方式&#xff09;面插值效果展示&#xff1a; 面内插值计算插点效果展示&#xff1a; 线插值效果展示&#xff1a; &#xff08;离屏渲染方式&#xff09;高密度线内插值计算效果展示&#xff1a; 相关代码&#xff1a; i…

docker二进制包部署(带arm版自动部署包)

文章目录 1.概述2.Docker二进制包下载3.安装脚本制作4.安装5.卸载6.注意事项7.分享一个arm版自动部署安装包8.懒人 X86 版安装包 1.概述 最近需要在Linux上部署docker&#xff0c;于是自己做了一个自动部署包。脚本的写法不区分X86或arm&#xff0c;通用的。 2.Docker二进制包…

网络安全和数据安全到底有什么区别?(非常详细)零基础入门到精通,收藏这一篇就够了

随着信息技术的迅猛发展&#xff0c;网络安全和数据安全已经成为当今社会不可忽视的重要议题。两者在保障信息系统安全、防范数据泄露和保障用户权益方面起着至关重要的作用。然而&#xff0c;尽管网络安全与数据安全在某些方面有着密切的联系&#xff0c;但它们在定义、目标和…

“八股文”:程序员的福音还是梦魇?

——一场关于面试题的“代码战争” 在程序员的世界里&#xff0c;“八股文”这个词儿可谓是“如雷贯耳”。不&#xff0c;咱们可不是说古代科举考试中的那种八股文&#xff0c;而是指程序员面试中的那些固定套路的题目。如今&#xff0c;各大中小企业在招聘程序员时&#xff0…

11.2.0.4 ADG故障 LGWR (ospid: 30945):terminating the instance due to error 4021

11.2.0.4 ADG无法连接&#xff0c;查看数据库为关闭状态&#xff0c;重新启动实例&#xff0c;应用日志后即可正常同步数据并打开到只读模式。 查看alert日志发现有以下报错&#xff1a; 0RA-04021:timeout occurred while waiting to lock obiectLGWR (ospid: 30945):termi…

矩阵、向量、张量 一文彻底理清!

矩阵&#xff1a;可理解为二维数组、二维张量 向量Vector&#xff1a;是只有一列的矩阵 张量&#xff1a;是矩阵向任意维度的推广。 机器学习经常会用到张量做变换&#xff0c;所以下文重点介绍张量。 可以通过.ndim查看numpy数据的张量维度。张量的维度&#xff08;dimens…