[OC学习笔记]KVO原理

2024-02-13 13:59
文章标签 学习 笔记 原理 oc kvo

本文主要是介绍[OC学习笔记]KVO原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

KVO介绍

KVO,全称为Key-Value observing,中文名为键值观察,KVO是一种机制,它允许将其他对象的指定属性的更改通知给对象。官方文档里说到:为了理解键值观察,必须首先了解键值编码(KVC)。
KVC是键值编码,在对象创建完成后,可以动态的给对象属性赋值,而KVO是键值观察,提供了一种监听机制,当指定的对象的属性被修改后,则对象会收到通知,所以可以看出KVO是基于KVC的基础上对属性动态变化的监听了。
KVO的主要好处是,不必每次属性更改时都实施自己的计划来发送通知。其定义明确的基础设施具有框架级支持,易于采用,通常不必向项目添加任何代码。此外,基础设施已经功能齐全,因此可以轻松支持单个属性的多个观察者以及依赖值。
与使用NSNotificationCenter的通知不同,KVO没有为所有观察者提供更改通知的中心对象。相反,当进行更改时,通知会直接发送到观察对象。NSObject提供了KVO的基本实现,您很少需要覆盖这些方法。

KVO与NSNotificatioCenter的区别

  • 相同点
    (1)两者的实现原理都是观察者模式,都是用于监听
    (2)都能实现一对多的操作
  • 不同点
    (1)KVO只能用于监听对象属性的变化,并且属性名都是通过NSString来查找,编译器不会自动检测对错和补全,会比较容易出错
    (2)NSNotification的发送监听(post)操作我们可以控制,KVO则是由系统控制
    (3)KVO可以记录新旧值变化

KVO使用

注册观察者

观察对象首先通过发送addObserver:forKeyPath:options:context:消息与观察到的对象一起注册,将自己作为要观察的属性的观察者和密钥路径传递给自己。观察者还指定了一个选项参数和一个上下文指针来管理通知。

[me addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

实现回调

当对象的观察属性值发生变化时,观察者会收到observeValueForKeyPath:ofObject:change:context:消息。所有观察者都必须实施这种方法。
观察对象提供触发通知的keyPath,本身作为相关对象,包含更改详细信息的字典,以及观察者注册此密钥路径时提供的上下文指针。
更改字典条目NSKeyValueChangeKindKey提供有关发生更改类型的信息。如果观察到的对象的值发生了变化,NSKeyValueChangeKindKey条目将返回NSKeyValueChangeSetting。根据注册观察者时指定的选项,更改字典中的NSKeyValueChangeOldKeyNSKeyValueChangeNewKey条目包含更改之前和之后的属性值。如果属性是对象,则直接提供该值。如果属性是标量或C结构,则该值包装在NSValue对象中(如KVC)。
如果观察到的属性是对多关系,NSKeyValueChangeKindKey条目还指示关系中的对象是否被插入、删除或替换为分别返回NSKeyValueChangeInsertionNSKeyValueChangeRemovalNSKeyValueChangeReplacement
NSKeyValueChangeIndexesKey的更改字典条目是一个NSIndexSet对象,指定更改关系中的索引。如果注册观察者时将NSKeyValueObservingOptionNewNSKeyValueObservingOptionOld指定为选项,则更改字典中的NSKeyValueChangeOldKeyNSKeyValueChangeNewKey条目是包含更改之前和之后相关对象值的数组。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {if ([keyPath isEqualToString:@"name"]) {NSLog(@"%@", change);}
}

移除观察者

可以通过向观察到的对象发送removeObserver:forKeyPath:context:消息来删除键值观察者,指定观察对象、键路径和上下文。

[me removeObserver:self forKeyPath:@"name" context:nil];

收到removeObserver:forKeyPath:context:消息后,观察对象将不再接收指定密钥路径和对象的任何observeValueForKeyPath:ofObject:change:context:的消息。
移除观察者时,需要注意:

  • 如果尚未注册为观察者,则要求删除为观察者,则会导致NSRangeException。您可以仅仅调用removeObserver:forKeyPath:context:一次,与addObserver:forKeyPath:options:context:相对应,或者如果这在您的应用程序中不可行,请在try/catch块中放置removeObserver:forKeyPath:context:调用以处理潜在的异常。
  • 观察者在dealloc时不会自动删除自己。观察的物体继续发送通知,忘记了观察者的状态。然而,与发送到已发布对象的任何其他消息一样,更改通知会触发内存访问异常。因此,您确保观察者在从内存中消失之前移除自己。
  • 该协议不提供询问物体是观察者还是被观察者的方法。构建您的代码,以避免与release相关的错误。一个典型的模式是在观察者的初始化期间(例如在initviewDidLoad中)注册为观察者,并在接触分配期间(通常在dealloc中)取消注册,确保正确配对和有序添加和删除消息,并且观察者在从内存中释放之前是未注册的状态。

总的来说,KVO注册观察者移除观察者是需要成对出现的,如果只注册,不移除,会出现类似野指针的崩溃。

Context

addObserver:forKeyPath:options:context:消息中的context指针包含任意数据,这些数据将在相应的更改通知中传递回观察者。您可以指定NULL,并完全依赖密钥路径字符串来确定更改通知的来源,但这种方法可能会给超类出于不同原因也观察相同密钥路径的对象带来问题。
一个更安全、更可扩展的方法是使用上下文来确保您收到的通知是针对您的观察者而不是超类的。
类中唯一命名的静态变量的地址是一个很好的context。在超类或子类中以类似方式选择的context不太可能重叠。您可以为整个类选择单个context,并依赖通知消息中的键路径字符串来确定更改的内容。或者,也可以为每个观察到的键路径创建一个不同的context,这完全绕过了字符串比较的需求,从而提高通知解析效率。
也就是说,context主要是用于区分不同对象的同名属性,从而在KVO回调方法中可以直接使用context进行区分,可以大大提升性能,以及代码的可读性。
官方文档给了两个代码示例:

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
- (void)registerAsObserverForAccount:(Account*)account {[account addObserver:selfforKeyPath:@"balance"options:(NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld)context:PersonAccountBalanceContext];[account addObserver:selfforKeyPath:@"interestRate"options:(NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld)context:PersonAccountInterestRateContext];
}

KVO的自动与手动触发

自动触发

返回NO,就监听不到,返回YES,表示可以监听。

// 自动
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {return YES;
}

手动触发

- (void)setName:(NSString *)name {//手动[self willChangeValueForKey:@"name"];_name = name;[self didChangeValueForKey:@"name"];
}

KVO的一对多

KVO中的一对多,意思是通过注册一个KVO观察者,可以监听多个属性的变化。
举个栗子:下载过程中不断更新总任务量和当前完成任务量,进度就是_currentData / _totalData
先加入监听:

[me addObserver:self forKeyPath:@"progress" options:NSKeyValueObservingOptionNew context:nil];

再去自己的类里面实现下面的方法:

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];if ([key isEqualToString:@"progress"]) {NSArray *affectingKeys = @[@"totalData", @"currentData"];keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];}return keyPaths;
}

或者实现一个遵循命名规则的类方法keyPathsForValuesAffecting<Key>以达到同样的效果:

+ (NSSet<NSString *> *)keyPathsForValuesAffectingProgress {return [NSSet setWithObjects:@"totalData", @"currentData", nil];
}

那么当totalDatacurrentData改动的时候,该值(progress)必须被通知。这是一种依赖方法。把progress写成如下:

- (float)progress {return _currentData / _totalData;
}

修改_currentData或者_totalData,会发现,通知了progress,就可以自动修改啦。
请添加图片描述

KVO观察可变数组

KVO是基于KVC基础之上的,所以可变数组如果直接添加数据,是不会调用setter方法的,所有对可变数组的KVO观察下面这种方式不生效的,即直接通过[me.dateArray addObject:@"1"];向数组添加元素,是不会触发KVO通知回调的。
请添加图片描述
将代码修改成如下方式:

[[me mutableArrayValueForKey:@"dataArray"] addObject:@"1"];

或者启动手动触发,就可以监听可变数组了,下面是监听结果
请添加图片描述
其中的kind表示键值变化的类型,是一个枚举,有以下4种:

/* Possible values in the NSKeyValueChangeKindKey entry in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information.
*/
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {NSKeyValueChangeSetting = 1,//设值NSKeyValueChangeInsertion = 2,//插入NSKeyValueChangeRemoval = 3,//移除NSKeyValueChangeReplacement = 4,//替换
};

可以看到,这里我们可变数组的类型就是插入了。

KVO底层原理探索

在官方文档里面,可以看到如下说明:
请添加图片描述
解释(oh,不,翻译)一下:

  • KVO是使用一种称为isa-swizzling的技术实现的。
  • 顾名思义,isa指针指向维护调度表的对象的类。此调度表实质上包含指向类实现的方法的指针以及其他数据。
  • 为对象的属性注册观察者时,将修改被观察对象的 isa 指针,其指向中间类而不是实际的类。因此,isa 指针的值不一定反映实例的实际类。
  • 永远不应该依赖isa指针来确定类的成员身份。相反,应该使用class方法来确定对象实例的类。

代码调试探索

属性与成员变量

我们为类创建属性和成员变量,分别注册KVO观察,看看有什么神奇效果。

[me addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
[me addObserver:self forKeyPath:@"memberVariable" options:NSKeyValueObservingOptionNew context:nil];
me.name = @"Billy";
me->memberVariable = @"Billy Member";

结果:
请添加图片描述
结论:KVO对成员变量不观察,只对属性观察,属性和成员变量的区别在于属性多一个 setter 方法,而KVO恰好观察的是setter方法。

中间类

前面我们也看到了,官方文档里的描述,注册KVO观察者后,观察对象的isa指针指向会发生改变
我们在控制台调试,注册观察者之前
请添加图片描述
实例对象meisa指针指向Person
注册观察者之后
请添加图片描述
实例对象meisa指针指向NSKVONotifying_Person。在注册观察者后,实例对象的isa指针指向由Person类变为NSKVONotifying_Person中间类,即实例对象的isa指针指向发生了变化。

中间类是子类

接下来判断关系,使用下面方法查看子类:

#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls {int count = objc_getClassList(NULL, 0);NSMutableArray *array = [NSMutableArray arrayWithObject:cls];Class* classes = (Class*)malloc(sizeof(Class)*count);objc_getClassList(classes, count);for (int i = 0; i < count; i++) {if (cls == class_getSuperclass(classes[i])) {[array addObject:classes[i]];}}free(classes);NSLog(@"%@'s classes = %@", cls, array);
}

我们观察注册观察者前后子类,可以发现NSKVONotifying_Person中间类是Person类的子类。
请添加图片描述

中间类的方法

我们通过下面的方法查看中间类的方法列表:

#pragma mark - 遍历方法-ivar-property
- (void)printClassAllMethod:(Class)cls {unsigned int count = 0;Method *methodList = class_copyMethodList(cls, &count);for (int i = 0; i<count; i++) {Method method = methodList[i];SEL sel = method_getName(method);IMP imp = class_getMethodImplementation(cls, sel);NSLog(@"%@ - %p",NSStringFromSelector(sel), imp);}free(methodList);
}

结果如下:
请添加图片描述
继续探究,这四个方法是继承还是重写呢?
Student中重写setName:方法,获取Student类的所有方法:
请添加图片描述
我们不难发现,获取到是重写过的方法。所以,可以说明上面的中间类的setName:方法是重写Person类的,继承的不会在子类遍历出来。

移除观察者中对中间类的思索

我们尝试移除观察者。先探究移除观察者后的isa指针变化:
请添加图片描述
可以看出,移除观察者以后实例对象的isa指向更改为Person类。接下来探究那么中间类被创建了之后,并且在后续的dealloc方法中被移除观察者之后,是否还存在?在dealloc以后,我们查看Person类的子类:
请添加图片描述
通过子类的打印结果可以看出,中间类一旦生成,没有移除,没有销毁,还在内存中,也许主要是考虑重用的想法,即中间类注册到内存中,为了考虑后续的重用问题,所以中间类一直存在,没有被销毁。也许也是害怕还有其他的观察者,所以没有注销。

中间类小结
  • 实例对象isa的指向在注册KVO观察者之后,由原有类更改为指向中间类
  • 中间类重写了观察属性的setter方法、classdealloc_isKVOA方法
  • dealloc方法中,移除KVO观察者之后,实例对象isa指向由中间类更改为原有类
  • 中间类从创建后,就一直存在内存中,不会被销毁

自定义KVO

在系统中,注册观察者和KVO响应属于响应式编程,是分开写的,在自定义为了代码更好的协调,使用block的形式,将注册和回调的逻辑组合在一起,即采用函数式编程
创建NSObject类的分类MYKVO

- (void)billyAddObserver:(NSObject *)observerkeyPath:(NSString *)keyPathoptions:(BillyKeyValueObservingOptions)optionscontext:(nullable void *)contexthandleBlock:(BillyKVOBlock)handleBlock;- (void)billyRemoveObserver:(NSObject *)observerkeyPath:(NSString *)keyPath;

注册观察者

#pragma mark - 验证setter方法是否存在
- (void)judgeSetterMethodFromeKeyPath:(NSString *)keyPath {Class superClass = object_getClass(self);//根据keyPath获得setter方法SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));Method setterMethod = class_getInstanceMethod(superClass, setterSelector);if (!setterMethod) {//不存在则抛出异常@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"没有找到当前%@的setter方法",keyPath] userInfo:nil];}
}
//动态生成KVO的派生类NSObservingKVO
- (Class)billyKVONotifingObservingKVOWithKeyPath:(NSString *)keyPath {NSString *oldClassName = NSStringFromClass(object_getClass(self));//kSafeKVOPrefix = @"SafeKVONotifying_"NSString *newClassName = [NSString stringWithFormat:@"%@%@", billyKVOPrefix, oldClassName];Class newClass = NSClassFromString(newClassName);if (newClass) {//防止重复创建return newClass;}//不存在则创建//申请classnewClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);//注册类objc_registerClassPair(newClass);//添加classSEL classSel = NSSelectorFromString(@"class");Method classMethod = class_getInstanceMethod([self class], classSel);const char *classType = method_getTypeEncoding(classMethod);class_addMethod(newClass, classSel, (IMP)billy_class, classType);//添加重写的setterSEL setterSel = NSSelectorFromString(setterForGetter(keyPath));Method setterMethod = class_getInstanceMethod([self class], setterSel);const char *setterType = method_getTypeEncoding(setterMethod);class_addMethod(newClass, setterSel, (IMP)billy_setter, setterType);//派生类dealloc,主要是为了不移除KVO监听时自动处理的SEL dealSel = NSSelectorFromString(@"dealloc");Method dealMethod = class_getInstanceMethod([self class], dealSel);const char *dealType = method_getTypeEncoding(dealMethod);class_addMethod(newClass, dealSel, (IMP)billy_dealloc, dealType);return newClass;
}
- (void)billyAddObserver:(NSObject *)observerkeyPath:(NSString *)keyPathoptions:(BillyKeyValueObservingOptions)optionscontext:(nullable void *)contexthandleBlock:(BillyKVOBlock)handleBlock {[self judgeSetterMethodFromeKeyPath:keyPath];//获得派生类Class newClass = [self billyKVONotifingObservingKVOWithKeyPath:keyPath];//修改isa指针指向派生类object_setClass(self, newClass);//保存信息BillyKVOManager *info = [[BillyKVOManager alloc] initWithObserver:observer keyPath:keyPath options:options handleBlock:handleBlock];NSMutableArray *infoArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(billyInfoArray));if (!infoArray) {infoArray = [NSMutableArray arrayWithCapacity:1];objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(billyInfoArray), infoArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}[infoArray addObject:info];
}

KVO响应

#pragma mark - 重写的setter
static void billy_setter(id self, SEL _cmd, id newValue) {NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));//获得原来的值id oldValue = [self valueForKey:keyPath];//消息转发,转发给父类处理(setKeyPath:)void (*billy_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;struct objc_super superStruct = {.receiver = self,.super_class = class_getSuperclass(object_getClass(self)),};billy_msgSendSuper(&superStruct, _cmd, newValue);//获得信息数据NSMutableArray *infoArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(billyInfoArray));for (BillyKVOManager *info in infoArray) {if ([info.keyPath isEqualToString:keyPath]) {if (info.handleBlock) {//有block回调info.handleBlock(info.observer, info.keyPath, oldValue, newValue);}//实现observeValueForKeyPath:可监听值的变化dispatch_async(dispatch_get_global_queue(0, 0), ^{NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];// 对新旧值进行处理if (info.options & BillyKeyValueObservingOptionNew) {[change setObject:newValue forKey:NSKeyValueChangeNewKey];}if (info.options & BillyKeyValueObservingOptionOld) {[change setObject:@"" forKey:NSKeyValueChangeOldKey];if (oldValue) {[change setObject:oldValue forKey:NSKeyValueChangeOldKey];}}//消息发送给观察者// (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)contextSEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
//                objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)void (*billy_msgSend)(id, SEL, NSString *, id, NSDictionary<NSKeyValueChangeKey, id> *, void *) = (void *)objc_msgSend;billy_msgSend(info.observer, observerSEL, keyPath, self, change, NULL);});}}
}

block回调和方法响应。

移除观察者

清空数组,以及isa指向更改。

- (void)billyRemoveObserver:(NSObject *)observer keyPath:(NSString *)keyPath {NSMutableArray *infoArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(billyInfoArray));if (infoArray.count <= 0) {return;}for (BillyKVOManager *info in infoArray) {if ([info.keyPath isEqualToString:keyPath]) {[infoArray removeObject:info];//清空数组objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(billyInfoArray), infoArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}}if (infoArray.count == 0) {//isa重新指向父类Class superClass = [self class];object_setClass(self, superClass);}
}

在子类中重写dealloc方法,当子类销毁时,会自动调用dealloc方法(在动态生成子类的方法中添加)

//派生类dealloc,主要是为了移除KVO监听时自动处理的
SEL dealSel = NSSelectorFromString(@"dealloc");
Method dealMethod = class_getInstanceMethod([self class], dealSel);
const char *dealType = method_getTypeEncoding(dealMethod);
class_addMethod(newClass, dealSel, (IMP)billy_dealloc, dealType);
#pragma mark - kvoDealloc
static void billy_dealloc(id self, SEL _cmd) {NSLog(@"派生类移除了");//获得父类Class superClass = [self class];//isa指针重新指向父类object_setClass(self, superClass);}

自定义KVO总结

注册观察者 & 响应

  1. 验证是否存在setter方法
  2. 保存信息
  3. 动态生成子类,需要重写class、setter方法
  4. 在子类的setter方法中向父类发消息,即自定义消息发送
  5. 让观察者响应

移除观察者

  1. 更改isa指向为原有类
  2. 重写子类的dealloc方法

总结

KVO的本质是什么?
利用runtime的API动态生成一个子类,并让实例对象的isa指向这个全新的子类,当修改实例变量对象的属性时候,在新子类的setter方法中会调用Foundation的_NSSetXXXValueAndNotify函数。willChangeValueForKey调用原来的setterdidChangeValueForKey:内部会触发监听器的监听方法。

这篇关于[OC学习笔记]KVO原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/705719

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识