【iOS】——消息传递底层实现

news2024/12/23 22:32:51

消息传递是什么

Objective-C是一种动态类型语言,这意味着在编译时并不确定对象的具体类型,而是在运行时决定。消息传递机制允许程序在运行时向对象发送消息,对象再决定如何响应这些消息。

当你通过对象调用方法时,例如像这样[obj someMethod]编译器会将其转换为一个消息发送的底层调用,通常是 objc_msgSend(obj, @selector(someMethod))。这个函数接受两个主要参数:方法的调用者方法选择器(也就是方法名)。

void objc_msgSend(id self, SEL cmd, ....)

第一 个参数代表接收者也就是方法调用者,第二个参数代表方法选择器(SEL 是选择子的类型)也就是方法的名字,后续参数就是消息中的 那些参数,其顺序不变。

选择子SEL

选择子(Selector)是用于表示方法名的数据类型。它是一个在运行时由编译器生成的唯一的标识符,用于在对象中查找并调用相应的方法

OC在编译时会根据方法的名字(包括参数序列),生成一个用来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么他们的ID就是相同的。所以不管是父类还是子类,名字相同那么ID就是一样的

IMP

IMP是一个函数指针,它指向方法的实际实现。当运行时系统找到了与选择器相匹配的方法时,它会获取该方法的IMP,然后调用这个函数指针来执行方法的代码。

IMP通常被声明为id返回类型和接受id类型的selfSEL类型的_cmd参数的函数指针。例如:

typedef id (*IMP)(id, SEL, ...);

SEL与IMP的关系

在运行时系统中,SELIMP是紧密相关的。当你调用一个方法时,运行时系统首先将方法名解析为SEL,然后使用这个SEL去查找与之对应的IMP。一旦找到IMP,运行时系统就会调用这个函数指针来执行方法的代码。

消息传递的流程

  • 首先,Runtime系统会通过obj的 isa 指针找到其所属的class

  • 接着在这个类的缓存中查找与选择器匹配的方法实现

  • 如果缓存中没找到接着在这个类的方法列表(method list)中查找与选择器(someMethod)匹配的方法实现(IMP)。

  • 如果在当前类中没有找到,Runtime会沿着类的继承链往它的 superclass 中查找,也是先查缓存再查方法列表,直到到达根类(通常为 NSObject)。

  • 一旦找到someMethod这个函数,就去执行它的实现IMP 。

  • 如果直到根类还是没找到就会进行消息转发流程。

消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。

objc_msgSend()的伪代码如下:

id objc_msgSend(id self, SEL _cmd, ...) {
  //获取当前对象的类对象用于在运行时获取方法实现
  Class class = object_getClass(self);
  //根据类对象和选择器_cmd获取IMP指针
  IMP imp = class_getMethodImplementation(class, _cmd);
  //判空IMP并返回该方法实现
  return imp ? imp(self, _cmd, ...) : 0;
}

上面代码之所以是伪代码是因为objc_msgSend 是用汇编语言写的,针对不同架构有不同的实现。汇编语言的效率比c/c++更快,它直接对寄存器进行访问和操作,相比较内存的操作更加底层效率更高。

其源代码比较多,下面是核心部分:

ENTRY _objc_msgSend       // 开始_objc_msgSend函数的定义
MESSENGER_START           // 标记消息传递的开始

NilTest NORMAL            // 检查self是否为nil,如果是nil则引发异常

GetIsaFast NORMAL         // 快速获取self对象的isa指针,isa指向对象的类信息
                          // r11 = self->isa

CacheLookup NORMAL        // 在缓存中查找IMP,如果找到则直接调用IMP
                          // 这里利用了方法缓存,以提高性能

NilTestSupport NORMAL     // 如果self是nil的备用处理

GetIsaSupport             // 如果需要,更全面地获取isa信息
                          // 这是为了支持特殊情况下的isa获取

// cache miss: go search the method lists
LCacheMiss:               // 缓存中没找到,开始在方法列表中查找IMP
                          // isa仍然在r11寄存器中

MethodTableLookup %a1, %a2 // 在方法表中查找与选择器匹配的IMP
                          // r11 = IMP,这里查找方法实现

cmp %r11, %r11            // 设置eq标志位,用于非 stret (simple) 的方法转发
                          // 这里比较r11与自身,实际上是设置条件码,用于判断是否需要转发

jmp *%r11                 // 跳转到IMP地址并执行
                          // 这里直接调用了方法实现

END_ENTRY _objc_msgSend   // 结束_objc_msgSend函数的定义

MethodTableLookup 宏是重点,负责在缓存没命中时在方法表中负责查找 IMP:

.macro MethodTableLookup

	MESSENGER_END_SLOW
	
	SaveRegisters

	// _class_lookupMethodAndLoadCache3(receiver, selector, class)

	movq	$0, %a1
	movq	$1, %a2
	movq	%r11, %a3
	call	__class_lookupMethodAndLoadCache3

	// IMP is now in %rax
	movq	%rax, %r11

	RestoreRegisters

.endmacro

从上面的代码可以看出方法查找 IMP 的工作交给了 OC 中的 _class_lookupMethodAndLoadCache3 函数,并将 IMP 返回(从 r11 挪到 rax)。最后在 objc_msgSend 中调用 IMP。

消息传递快速查找

  • 首先检查消息接收者receiver是否存在,不存在则不做任何处理。

  • 接着通过receiver的isa指针找到对应的class类对象,

  • cache中获取buckets,从buckets中对比参数sel,看在缓存里有没有同名方法如果buckets中有对应的SEL则返回它对应的IMP

  • 如果在缓存中没有找到匹配的方法选择子,则执行慢速查找过程,即调用 _objc_msgSend_uncached 函数,并进一步调用 _lookUpImpOrForward 函数进行全局的方法查找。

简单来说快速查找就是在所属类的缓存中查找SEL对应的IMP指针

buckets是cache中的结构体,bucket_t的结构体中存储了一个unsigned long和一个IMP。IMP是一个函数指针,指向了一个方法的具体实现。
cache_t中的bucket_t *_buckets其实就是一个散列表,用来存储Method的链表。

在这里插入图片描述

消息传递慢速查找

当消息发送的快速查找过程无法找到匹配的方法实现时,就会进入 _lookUpImpOrForward 函数根据继承链和协议信息逐级查找方法。

  • 从本类的 method list **(二分查找/遍历查找)**查找imp
  • 从本类的父类的cache查找imp**(汇编)**
  • 从本类的父类的method list **(二分查找/遍历查找)**查找imp …继承链遍历…(父类->…->根父类)里找cache和method list的imp
  • 若上面环节有任何一个环节查找到了imp,跳出循环,缓存方法到本类的cache,并返回imp
  • 直到查找到nil,指定imp为消息转发,跳出循环,执行动态决议resolveMethod_locked(消息转发的内容)

简单来说慢速查找就是在所属类的方法列表中查找SEL对应的IMP指针,没找到就沿着继承链一直查找方法缓存和方法列表,找到就返回IMP,没找到就执行动态决议

lookUpImpOrForward函数

前面提到的 _class_lookupMethodAndLoadCache3 函数其实就是简单的调用了 lookUpImpOrForward 函数:

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

注意 lookUpImpOrForward 调用时使用缓存参数传入为 NO,因为之前快速查找的时候已经尝试过查找缓存了

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) 实现了一套查找 IMP 的标准路径,也就是在消息转发(Forward)之前的逻辑。

下面是lookUpImpOrForward函数源码:

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
    const IMP forward_imp = (IMP)_objc_msgForward_impcache; // 定义转发的IMP
    IMP imp = nil; // 初始化IMP为nil
    Class curClass;

    runtimeLock.assertUnlocked(); // 确认运行时锁未被持有

    // 如果类未初始化,设置LOOKUP_NOCACHE,防止缓存单个条目的情况
    if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }

    runtimeLock.lock(); // 锁住运行时锁

    // 验证类是否是已知的类
    checkIsKnownClass(cls);
    // 确保类已实现和初始化
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);

    curClass = cls; // 当前类

    for (unsigned attempts = unreasonableClassCount(); ;) {
        // 如果类的缓存是常量优化缓存
      // 再一次从cache查找imp
      // 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
        if (curClass->cache.isConstantOptimizedCache(true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel); // 从缓存中查找IMP
            if (imp) goto done_unlock; // 如果找到IMP,结束循环
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // 从当前类的方法列表中查找匹配的选择器
            method_t *meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false); // 获取IMP
                goto done;
            }
            // 每次判断都会把curClass的父类赋值给curClass
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                imp = forward_imp; // 使用转发IMP
                break;
            }
        }

        // 防止超类链中出现循环
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // 在超类缓存中查找IMP
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // 在超类中发现转发条目,停止搜索
            break;
        }
        if (fastpath(imp)) {
            // 在超类中找到方法,缓存结果
            goto done;
        }
    }

    // 尝试方法解析器
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == )) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass); // 填充缓存
    }

done_unlock:
    runtimeLock.unlock(); // 解锁

    // 如果设置LOOKUP_NIL并且IMP是转发IMP,则返回nil
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp; // 返回找到的IMP
}

总体的流程如下:

首先检查接收者 inst 是否为空,如果为空则直接返回空。

接下来,代码根据接收者的类对象 cls 进行一系列的处理和查找操作,以找到适当的方法实现 imp。包括:

  • 检查类对象是否已经初始化,如果尚未初始化则将 LOOKUP_NOCACHE 标志添加到 behavior 中,避免缓存查找。

  • 通过 realizeAndInitializeIfNeeded_locked 函数对类对象进行实例化和初始化处理,确保类对象已经准备就绪。

  • 使用循环逐级查找方法实现,包括在类的缓存中查找、在类的方法列表中查找、在父类链中查找。如果找到了匹配的方法实现,则跳转到 done 标签处。

  • 如果在查找过程中找不到匹配的方法实现,则说明需要进行消息转发。将消息转发的默认实现 forward_imp 赋给 imp。

  • 如果设置了 LOOKUP_RESOLVER 标志,说明需要调用方法解析器进行进一步处理,跳转到 resolveMethod_locked 函数进行解析。

  • 在查找或转发结束后,如果未设置 LOOKUP_NOCACHE 标志,将找到的方法实现 imp 缓存到类对象的缓存中。

  • 代码解锁runtime锁,根据需要返回找到的方法实现 imp 或空值

在循环中,首先检查当前类对象的缓存是否是常量优化缓存(isConstantOptimizedCache)。如果是常量优化缓存,代码尝试从缓存中获取方法实现(cache_getImp(curClass, sel))。如果成功获取到方法实现,则跳转到 done_unlock 标签处,结束查找。

如果当前类对象的缓存不是常量优化缓存,代码继续执行。通过调用 getMethodNoSuper_nolock 函数在当前类对象的方法列表中查找方法(meth = getMethodNoSuper_nolock(curClass, sel))。如果找到匹配的方法,则获取对应的方法实现(imp = meth->imp(false)),跳转到 done 标签处,结束查找。

如果在当前类对象的方法列表中没有找到匹配的方法实现,代码继续执行。将当前类对象的父类赋值给 curClass,并判断是否为 nil。如果父类为 nil,说明已经到达了继承链的顶端,没有找到匹配的方法实现。此时将默认的转发实现 forward_imp 赋给 imp,并跳出循环。

在循环的每次迭代中,会将 attempts 的值减一,表示尚未完成的查找次数。如果 attempts 的值减到零,则说明类对象的继承链中存在循环,这是不合理的。此时会触发一个错误,终止程序执行。

如果在当前类对象的缓存中找到了转发的条目(imp == forward_imp),表示在父类的缓存中找到了转发的方法实现。这时会停止循环,但不会将转发的方法实现缓存,而是先调用方法解析器来处理。

最后,在循环结束后,会根据需要将找到的方法实现缓存到类对象的缓存中,然后解锁运行时锁,并根据需要返回找到的方法实现或空值。

动态决议 resolveMethod_locked

当慢速查找依然没有找到IMP时,会进入方法动态解析阶段,源码如下:

// 尝试方法解析器
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

这里调用了resolveMethod_locked方法,下面是它的源代码:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    //**加锁**
    runtimeLock.unlock();
    
    //**判断是否是元类**
    if (! cls->isMetaClass()) {
        //**不是元类,调用resolveInstanceMethod方法**
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
  
        //**是元类,调用resolveClassMethod**
        resolveClassMethod(inst, sel, cls);
        //**如果调用上面的方法还没有找到,尝试调用resolveInstanceMethod**
        //**原因是根据isa的继承链,根元类的父类是NSObject,所以在元类中如果没有找到**
        //**最后可能会在NSObjct中找到目标方法**
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
    //**重新调用lookUpImpOrForwardTryCache方法,返回方法查找流程**
    //**因为已经进行过一次动态方法决议,下次将不会再进入,所以不会造成死循环**
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

  • 首先判断进行解析的是否是元类

  • 如果不是元类,则调用_class_resolveInstanceMethod进行实例方法动态解析

  • 如果是元类,则调用_class_resolveClassMethod进行类方法动态解析

  • b完成类方法动态解析后,再次查询cls中的imp,如果没有找到,则进行一次对象方法动态解析

  • 最后执行 lookUpImpOrForwardTryCache函数

    resolveInstanceMethodresolveClassMethod也称为方法的动态决议。

    resolveInstanceMethod方法

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    //**如果目标类没有初始化,直接报错**
    ASSERT(cls->isRealized());
    //**创建一个方法名为resolveInstanceMethod的SEL**
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    //**判断resolveInstanceMethod是否在目标类中实现**
    //**如果我们的类是继承自NSObject的话,那么这个判断就永远为false**
    //**因为在NSObject中已经默认实现了resolveInstanceMethod方法**
    //**因为是去cls->ISA也就是元类中查找,所以我们可以断定,resolveInstanceMethod是个类方法**
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }
    //**强制类型转换objc_msgSend**
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //**通过objc_msgSend方法调用resolveInstanceMethod**
    bool resolved = msg(cls, resolve_sel, sel);
    
    //**拼接上LOOKUP_NIL参数后,重新调用方法查找流程**
    //**虽然调用了resolveInstanceMethod,但是这个返回值是bool**
    //**所以我们要获取对应的imp,还是需要通过方法查找流程**
    //**如果通过resolveInstanceMethod添加了方法,就缓存在类中**
    //**没添加,则缓存forward_imp**
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
    //**组装相应的信息**
    if (resolved  &&  PrintResolving) {
        if (imp) {
            ………
        }
        else {
            ………
        }
    }
}

首先创建一个方法名为resolveInstanceMethodSEL对象resolve_sel;

然后判断resolve_sel是否实现,如果继承NSObject则必定已经实现,这里通过cls->ISA可以知道,resolveInstanceMethod是个类方法

通过objc_ msgSend直接调用resolveInstanceMethod方法,因为是直接对cls发送消息,所以也可以看出resolveInstanceMethods类方法;

调用lookUpImpOrNilTryCache方法,重新返回到方法查找的流程当中去;

resolveClassMethod方法

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    //**判断resolveClassMethod是否实现,NSObject中默认实现**
    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }
    //**获取目标类**
    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    //**强制类型转换objc_msgSend**
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //**通过objc_msgSend调用类中的resolveClassMethod方法**
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    //**拼接上LOOKUP_NIL参数后,重新调用方法查找流程**
    //**类方法实际上就是元类对象中的对象方法,所以可以通过方法查找流程在元类中查找**
    //**如果通过resolveClassMethod添加了,就缓存方法在元类中**
    //**没添加,则缓存forward_imp**
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
           ……
        }
        else {
           ……
        }
    }
}

这个方法与resolveInstanceMethod比较类似,如果通过resolveClassMethod方法添加了目标imp,则将其缓存在目标元类中,否则缓存forward_imp

lookUpImpOrNilTryCache方法

resolveInstanceMethodresolveClassMethod中都会调用的lookUpImpOrNilTryCache方法

extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    //**这里behavior没传,所以是默认值0**
    //**behavior | LOOKUP_NIL = 0 | 4 = 4**
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}

给参数behavior拼接上LOOKUP_NIL然后调用_lookUpImpTryCache方法

lookUpImpTryCache方法

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();
    //**判断类是否初始化**
    if (slowpath(!cls->isInitialized())) {
        //**没有初始化直接调用lookUpImpOrForward**
        //**里面针对没初始化的类,有相关处理**
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    //**去缓存中,查找sel是否有对应的imp**
    IMP imp = cache_getImp(cls, sel);
    //**找到了则跳去done**
    if (imp != NULL) goto done;
    //**没找到继续往下走,去共享缓存中查找**
#if CONFIG_USE_PREOPT_CACHES
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    //**没找到对应的imp,调用lookUpImpOrForward方法查找,behavior = 4**
    //** 4 & 2 = 0 ,所以这次方法查找不会再次进行动态方法决议**
    //**将_objc_msgForward_impcache缓存起来,方便下次直接返回**
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    //**命中缓存中,并且sel对应的imp为_objc_msgForward_impcache**
    //**说明动态方法决议已经执行过,且没有添加imp,则直接返回空**
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    //**说明动态方法决议中添加了对应的imp**
    return imp;
}

  • 首先判断类是否初始化,如果没有初始化则直接调用lookUpImpOrForward,里面有针对没初始化的类进行相应的处理;

  • 然后去缓存中进行方法的快速查找,找到了就去done

  • 缓存中没找到,如果支持共享缓存,则去共享缓存中查找

  • 都没有查找到,则通过慢速方法查找去查找方法,由于behavior 的值发生改变,这次慢速查找不会再次调用动态方法决议

  • done流程中,如果已经执行过动态方法决议且并没有添加imp,则缓存中的sel对应imp_objc_msgForward_impcache,这时直接返回nil。否则返回添加的imp实现。

如果系统在动态决议阶段没有找到实现,就会进入消息转发阶段。

在这里插入图片描述

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

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

相关文章

React 从入门到实战 一一开发环境基础搭建(小白篇)

React 从入门到实战一一开发环境基础搭建(小白篇) React 介绍什么是 react ?react 主要功能react 框架特点 开发工具渲染测试 React 介绍 最近两年,react 也愈来愈火热,想要在里面分一杯羹,那肯定逃不过 r…

CentOS 7开启SSH连接

1. 安装openssh-server 1.1 检查是否安装openssh-server服务 yum list installed | grep openssh-server如果有显示内容,则已安装跳过安装步骤,否则进行第2步 1.2 安装openssh-server yum install openssh-server2. 开启SSH 22监听端口 2.1 打开ssh…

阿里云盾占用资源的问题AliYunDun,AliYunDunUpdate

目录 1.关闭AliYunDunUpdate,AliYunDun,AliYunDunMonitor。 2.发现报错如下 3.打开阿里云安全中心控制台 4.成功解决 2.开启云盾命令 “如果您在解决类似问题时也遇到了困难,希望我的经验分享对您有所帮助。如果您有任何疑问或者想分享您…

【考研数学】线代满分经验分享+备考复盘

我一战二战复习都听了李永乐的线代课,二战的时候只听了一遍强化,个人感觉没有很乱,永乐大帝的课逻辑还是很清晰的。 以下是我听向量这一章后根据听课内容和讲义例题总结的部分思维导图,永乐大帝讲课的时候也会特意点到线代前后联…

spark shell

1.进行shell命令行 spark-shell 2.创建RDD 2.1 读取文件创建RDD 2.1.1读取linux文件系统的文件创建RDD --需要保证每一个worker中都有该文件 val data1 sc.textFile("file:/opt/file/word.txt") 2.1.2读取hdfs文件系统上的文件创建RDD val data2sc.textFile("…

基于dcm4chee搭建的PACS系统讲解(一)docker搭建精简版

文章目录 知识点PACSdcm4chedcm4chee部署dcm4chee方式 docker部署docker编排 总结 最近项目开始需要用到PACS系统,于是研究了一番,选用了dcm4chee搭建PACS系统,抛出 dcm-arc-light的git地址 。 知识点 PACS Picture Archiving and Communic…

视频压缩文件太大了怎么缩小?怎么压缩视频大小?视频压缩方法:10个!(宝藏)

视频压缩文件太大了怎么缩小?让我看看是谁下班之后不是一手刷手机短视频,顺便葛优躺在沙发上的?互联网发展到现在,视频已成为我们生活中不可或缺的一部分。不管是视频录制还是视频缓存,视频文件体积越来越庞大&#xf…

.net6 core Worker Service项目,使用Exchange Web Services (EWS) 分页获取电子邮件收件箱列表,邮件信息字段

Program.cs 安装包:Microsoft.AspNetCore.Hosting.WindowsServices、Microsoft.Extensions.Hosting、Microsoft.Extensions.Hosting.WindowsServices、Microsoft.Extensions.Logging.Log4Net.AspNetCore 新建Configs/log4net.config using Com.Chinahorn.Exchange.W…

鸿蒙开发StableDiffusion绘画应用

Stable Diffusion AI绘画 基于鸿蒙开发的Stable Diffusion应用。 Stable Diffusion Server后端代码 Stable Diffusion 鸿蒙应用代码 AI绘画 ​ 使用Axios发送post网络请求访问AI绘画服务器 api ,支持生成图片保存到手机相册。后端服务是基于flaskStable Diffusion …

MySQL实现主从复制的步骤,包括配置读写分离的方法。—— 慧哥充电桩开源平台

下载源码 【慧哥开源充电桩平台】 https://liwenhui.blog.csdn.net/article/details/134773779?spm1001.2014.3001.5502 MySQL主从复制是一种常见的数据备份和读写分离策略。下面是实现MySQL主从复制的步骤: 配置主服务器(Master)&#xff1…

鸿蒙开发入门——ArkTS语法简介(万字简介)

ArkTS 作为鸿蒙开发的编程语言,我们先来一图看看这个语言,我们可以看到ArkTS是在TS(TypeScript)的基础上改造的,而TS又是在JS(JavaSript)上改造的,一句话总结就是ArkTS是TS的超集&a…

为 android编译 luajit库、 交叉编译

时间:20200719 本机环境:iMac2017 macOS11.4 参考: 官方的文档:Use the NDK with other build systems 写在前边:交叉编译跟普通编译类似,无非是利用特殊的编译器、链接器生成动态或静态库; make 本质上是按照 Make…

鸿蒙 next 5.0 版本页面跳转传参 接受参数 ,,接受的时候 要先定义接受参数的类型, 代码可以直接CV使用 [教程]

1, 先看效果 2, 先准备好两个页面 index 页面 传递参数 import router from ohos.routerEntry Component struct Index {Statelist: string[] [星期一, 星期二,星期三, 星期四,星期五]StateactiveIndex: number 0build() {Row() {Column({ space: 10 }) {ForEach(this.list,…

笔记 3 : 继续彭老师课本第 3 章的 arm 的汇编指令

(26) 指令 LDR : (27) STR : 可见,从语法上将, ! 提示编译器进行更复杂的编译,对应内涵更复杂的指令。 (28) LDR 与 STR 指令还可…

【银河麒麟服务器操作系统】java进程oom现象分析及处理建议

了解银河麒麟操作系统更多全新产品,请点击访问麒麟软件产品专区:https://product.kylinos.cn 现象描述 某服务器系统升级内核至4.19.90-25.22.v2101版本后仍会触发oom导致java进程被kill。 现象分析 oom现象分析 系统messages日志分析,故…

pikachu之暴力破解

1基于表单的暴力破解 随便输入然后抓包 选中添加账号密码 添加分别添加payload1,2,的字典 开始攻击 2验证码绕过on server 和基于表单的暴力破解相比,多了一个验证码功能 这个验证码是前端的验证码(和前面那个一样选中添加账号密码…

计算机网络入门 -- 常用网络协议

计算机网络入门 – 常用网络协议 1.分类 1.1 模型回顾 计算机网络细分可以划为七层模型,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而上三层可以划为应用层中。 1.2 分类 1.2.1 应用层 为用户的应用进程提供网络通信服务&#xff0…

第一百七十三节 Java IO教程 - Java缓冲区读写

Java IO教程 - Java缓冲区读写 缓冲区读取 有两种方法从缓冲区读取数据: 绝对位置相对位置 使用四个版本重载的get()方法用于从缓冲区读取数据。 get(int index)返回给定索引处的数据。 get()从缓冲区中的当前位置返回数据,并将位置增加1。 get(byte [] dest…

vmware配置centos+配置静态ip联网+更换镜像

centos7配置参考【实战】VMware17虚拟机以及Centos7详细安装教程-CSDN博客 ip配置步骤: 先更改编辑虚拟网络编辑器中的内容 就按照还原默认设置来,设定后就是以上内容,然后一定要记住子网ip和子网掩码 接下来就是NAT设置: 网关…

Spring Boot集成SFTP快速入门Demo

1.什么是SFTP? SFTP(SSH File Transfer Protocol,也称 Secret File Transfer Protocol),是一种基于SSH(安全外壳)的安全的文件传输协议。使用SFTP协议可以在文件传输过程中提供一种安全的加密算…