【iOS】cache_t分析

前言

之前分析类的结构的时候,有遇到一个cache_t,当时说是用来保存方法缓存的结构,这篇文章来从源码详细介绍一下cache_t

概览cache_t

cache_t结构

类在底层的结构如之前所述,存在着cache_t属性,而cache_t的结构如下:

struct cache_t {
private:// explicit_atomic 显示原子性,目的是为了能够 保证 增删改查时 线程的安全性explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8union {struct {//一共 8explicit_atomic<mask_t>    _maybeMask; //4
#if __LP64__uint16_t                   _flags;//2
#endifuint16_t                   _occupied;//2};explicit_atomic<preopt_cache_t *> _originalPreoptCache;};#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED//macOS、模拟器 -- 主要是架构区分// _bucketsAndMaybeMask is a buckets_t pointer// _maybeMask is the buckets maskstatic constexpr uintptr_t bucketsMask = ~0ul;static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRSstatic constexpr uintptr_t maskShift = 48;static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHESstatic constexpr uintptr_t preoptBucketsMarker = 1ul;static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker;
#endif
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 //64位真机// _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits// _maybeMask is unused, the mask is stored in the top 16 bits.// How much the mask is shifted by.static constexpr uintptr_t maskShift = 48;// Additional bits after the mask which must be zero. msgSend// takes advantage of these additional bits to construct the value// `mask << 4` from `_maskAndBuckets` in a single instruction.static constexpr uintptr_t maskZeroBits = 4;// The largest mask value we can store.static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;// The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;// Ensure we have enough bits for the buckets pointer.static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS,"Bucket field doesn't have enough bits for arbitrary pointers.");#if CONFIG_USE_PREOPT_CACHESstatic constexpr uintptr_t preoptBucketsMarker = 1ul;
#if __has_feature(ptrauth_calls)// 63..60: hash_mask_shift// 59..55: hash_shift// 54.. 1: buckets ptr + auth//      0: always 1static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe;static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {uintptr_t value = (uintptr_t)cache->shift << 55;// masks have 11 bits but can be 0, so we compute// the right shift for 0x7fff rather than 0xffffreturn value | ((objc::mask16ShiftBits(cache->mask) - 1) << 60);}
#else// 63..53: hash_mask// 52..48: hash_shift// 47.. 1: buckets ptr//      0: always 1static constexpr uintptr_t preoptBucketsMask = 0x0000fffffffffffe;static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {return (uintptr_t)cache->hash_params << 48;}
#endif
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4//非64位 真机// _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits// _maybeMask is unused, the mask length is stored in the low 4 bitsstatic constexpr uintptr_t maskBits = 4;static constexpr uintptr_t maskMask = (1 << maskBits) - 1;static constexpr uintptr_t bucketsMask = ~maskMask;static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#else
#error Unknown cache mask storage type.
#endifbool isConstantEmptyCache() const;bool canBeFreed() const;mask_t mask() const;#if CONFIG_USE_PREOPT_CACHESvoid initializeToPreoptCacheInDisguise(const preopt_cache_t *cache);const preopt_cache_t *disguised_preopt_cache() const;
#endifvoid incrementOccupied();void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);void collect_free(bucket_t *oldBuckets, mask_t oldCapacity);static bucket_t *emptyBuckets();static bucket_t *allocateBuckets(mask_t newCapacity);static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));public:// The following four fields are public for objcdt's use only.// objcdt reaches into fields while the process is suspended// hence doesn't care for locks and pesky little details like this// and can safely use these.unsigned capacity() const;struct bucket_t *buckets() const;Class cls() const;#if CONFIG_USE_PREOPT_CACHESconst preopt_cache_t *preopt_cache() const;
#endifmask_t occupied() const;void initializeToEmpty();#if CONFIG_USE_PREOPT_CACHESbool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = (uintptr_t)&_objc_empty_cache) const;bool shouldFlush(SEL sel, IMP imp) const;bool isConstantOptimizedCacheWithInlinedSels() const;Class preoptFallbackClass() const;void maybeConvertToPreoptimized();void initializeToEmptyOrPreoptimizedInDisguise();
#elseinline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }inline bool shouldFlush(SEL sel, IMP imp) const {return cache_getImp(cls(), sel) == imp;}inline bool isConstantOptimizedCacheWithInlinedSels() const { return false; }inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); }
#endifvoid insert(SEL sel, IMP imp, id receiver);void copyCacheNolock(objc_imp_cache_entry *buffer, int len);void destroy();void eraseNolock(const char *func);static void init();static void collectNolock(bool collectALot);static size_t bytesForCapacity(uint32_t cap);#if __LP64__bool getBit(uint16_t flags) const {return _flags & flags;}void setBit(uint16_t set) {__c11_atomic_fetch_or((_Atomic(uint16_t) *)&_flags, set, __ATOMIC_RELAXED);}void clearBit(uint16_t clear) {__c11_atomic_fetch_and((_Atomic(uint16_t) *)&_flags, ~clear, __ATOMIC_RELAXED);}
#endif#if FAST_CACHE_ALLOC_MASKbool hasFastInstanceSize(size_t extra) const{if (__builtin_constant_p(extra) && extra == 0) {return _flags & FAST_CACHE_ALLOC_MASK16;}return _flags & FAST_CACHE_ALLOC_MASK;}size_t fastInstanceSize(size_t extra) const{ASSERT(hasFastInstanceSize(extra));//Gcc的内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0if (__builtin_constant_p(extra) && extra == 0) {return _flags & FAST_CACHE_ALLOC_MASK16;} else {size_t size = _flags & FAST_CACHE_ALLOC_MASK;// remove the FAST_CACHE_ALLOC_DELTA16 that was added// by setFastInstanceSize//删除由setFastInstanceSize添加的FAST_CACHE_ALLOC_DELTA16 8个字节return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);}}void setFastInstanceSize(size_t newSize){// Set during realization or construction only. No locking needed.uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;uint16_t sizeBits;// Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16// to yield the proper 16byte aligned allocation size with a single masksizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;sizeBits &= FAST_CACHE_ALLOC_MASK;if (newSize <= sizeBits) {newBits |= sizeBits;}_flags = newBits;}
#elsebool hasFastInstanceSize(size_t extra) const {return false;}size_t fastInstanceSize(size_t extra) const {abort();}void setFastInstanceSize(size_t extra) {// nothing}
#endif
};

在cache_t的机构当中,有一个极为重要的属性,就是_bucketsAndMaybeMask,它是一个bucket_t类型的结构体指针。

查看bucket_t的结构可以发现它大致存放了imp,实际上bucket作为一个桶,就是用来存放imp方法实现以及它的key,所以结合以上两个结构体可知,cache中缓存的正是sel-imp

在cache_t结构体中提供了获取_buckets属性的方法buckets(),获取了_buckets属性,就可以获取sel-imp了,这两个的获取在bucket_t结构体中也提供了相应的获取方法sel() 以及imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls)。

在没有执行方法调用时,cache中没有缓存,执行了一次方法调用,cache中就有了一个缓存,即调用一次方法就会缓存一次方法

深入cache_t

cache_t中有一个函数叫incrementOccupied(),具体实现为:

全局搜索这个函数,发现它只在cache_t的insert方法有调用

insert方法,顾名思义其实就是往cache中插入sel-imp的方法。全局搜索cache_t::insert方法,发现在写入之前,还有一步操作就是查找sel-imp,即cache的读取

分析insert方法

insert方法的源码如下:

void cache_t::insert(SEL sel, IMP imp, id receiver)
{runtimeLock.assertLocked();// Never cache before +initialize is doneif (slowpath(!cls()->isInitialized())) {return;}if (isConstantOptimizedCache()) {_objc_fatal("cache_t::insert() called with a preoptimized cache for %s",cls()->nameForLogging());}#if DEBUG_TASK_THREADSreturn _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCKmutex_locker_t lock(cacheUpdateLock);
#endifASSERT(sel != 0 && cls()->isInitialized());// Use the cache as-is if until we exceed our expected fill ratio.mask_t newOccupied = occupied() + 1;//没有属性赋值的情况下 occupied() = 0, newOccupied = 1unsigned oldCapacity = capacity(), capacity = oldCapacity;if (slowpath(isConstantEmptyCache())) {//小概率发生的,即当 occupied() = 0时,即创建缓存,创建属于小概率事件// Cache is read-only. Replace it.if (!capacity) capacity = INIT_CACHE_SIZE;//初始化时,capacity = 4 (1<<2)reallocate(oldCapacity, capacity, /* freeOld */false);//开辟空间//到目前为止,if的流程的操作都是初始化创建}else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {// Cache is less than 3/4 or 7/8 full. Use it as-is.//如果小于等于占用内存的3/4,什么都不用做/*第一次时,申请开辟的内存是4个,如果此时已经有3个从bucket插入到cache里面,再插入一个,就是4个.当大于4即当前下标是4,就数组越界了,这是肯定不行的,所以需要在原理的容量上进行两倍扩容*/}
#if CACHE_ALLOW_FULL_UTILIZATIONelse if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {// Allow 100% cache utilization for small buckets. Use it as-is.}
#endifelse {//如果超出了3/4,则需要扩容(两倍扩容) -- 即 occupied 为2时,就没有进去了//扩容算法: 有capacity时,扩容两倍,没有capacity就初始化为4capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;if (capacity > MAX_CACHE_SIZE) {capacity = MAX_CACHE_SIZE;}//走到这里表示:曾经有,但是已经满了,需要重新梳理reallocate(oldCapacity, capacity, true);// 内存 扩容完毕}bucket_t *b = buckets();mask_t m = capacity - 1; //mask = capacity - 1mask_t begin = cache_hash(sel, m);//求cache哈希,即哈希下标---通过哈希算法函数计算 sel存储的下标mask_t i = begin;// Scan for the first unused slot and insert there.// There is guaranteed to be an empty slot.//如果存在了哈希冲突,则从冲突的下标开始遍历 即进行do-while循环do {//如果当前遍历的下标拿不到sel,即表示当前下标没有存储selif (fastpath(b[i].sel() == 0)) {//则将sel存储进去,并将对应的 occupied标记++,即从0变为1incrementOccupied();b[i].set<Atomic, Encoded>(b, sel, imp, cls());return;}//如果当前哈希下标的sel等于准备插入的sel,则直接返回if (b[i].sel() == sel) {// The entry was added to the cache by some other thread// before we grabbed the cacheUpdateLock.return;}//如果当前计算的哈希下标已经存储了sel,且两个sel不相等,需要重新进行哈希计算 得到新的下标} while (fastpath((i = cache_next(i, m)) != begin));bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

insert的代码很长,但是其实主要可以分为三个部分:

  • 第一步,计算当前缓存占用量

  • 第二部,根据缓存占用量判断执行的操作

  • 针对需要存储的bucket进行内部的sel-imp赋值

下面我们来详细解释每个部分各自是如何实现的

计算当前缓存占用量

刚刚incrementOccupied的实现里有一步occupied的自增操作,这个occupied其实就代表着当前缓存中保存的sel-imp对的数量。

insert方法就是根据occupied的值计算出当前的缓存占用量,当属性未赋值及无方法调用时,此时的occupied()为0,而newOccupied为1

这里有几个需要注意的点:

  • alloc申请空间时,此时的对象已经创建,如果再调用init方法,occupied也会+1

  • 当有属性赋值时,会隐式调用set方法,occupied也会增加,即有几个属性赋值,occupied就会在原有的基础上加几个

  • 有方法调用时,occupied也会增加,即有几次调用,occupied会在原有的基础上加几个

根据缓存占用量判断执行的操作

如果是第一次创建,那么磨人开辟四个sel-imp的空间

如果缓存占用量小于等于3/4,就不做任何处理

而如果缓存占用量超过了3/4,需要进行两倍扩容以及重新开辟空间

reallocate方法

在判断完操作之后,如果是第一次创建或者两倍扩容,会用到一个方法来开辟空间,这个方法就是reallocate。

这个方法主要有以下几步:

allocateBuckets方法:向系统申请开辟内存,即开辟bucket,此时的bucket只是一个临时变量。 setBucketsAndMask方法:将临时的bucket存入缓存中,此时的存储分为两种情况:

1.如果是真机,根据bucket和mask的位置存储,并将occupied占用设置为0

2.如果不是真机,正常存储bucket和mask,并将occupied占用设置为0

如果有旧的buckets,需要清理之前的缓存,即调用collect_free方法

可以看到collect_free方法就是创建垃圾回收的空间,然后把需要回收的sel-imp放进去,最后进行回收。

其中_garbage_make_room方法创建垃圾回收空间的逻辑如下图:

如果是第一次,需要分配回收空间 如果不是第一次,则将内存段加大,即原有内存*2

针对需要存储的bucket进行内部imp和sel赋值

这个部分就是通过哈希算法来计算sel-imp的哈希下标:

  • 如果哈希下标的位置未存储sel,即该下标位置获取sel等于0,此时将sel-imp存储进去,并将occupied占用大小加1

  • 如果当前哈希下标存储的sel 等于 即将插入的sel,则直接返回

  • 如果当前哈希下标存储的sel 不等于 即将插入的sel,则重新经过cache_next方法 即哈希冲突算法,重新进行哈希计算,得到新的下标,再去对比进行存储

哈希算法和解决哈希冲突的源码如下:

补充

何为_mask?

_mask是指掩码数据,用于在哈希算法或者哈希冲突算法中计算哈希下标,其中mask等于capacity - 1

何为_occupied?

这个其实前面已经解释过了,这里再汇总讲一遍:

_occupied表示哈希表中 sel-imp 的占用大小 (即可以理解为分配的内存中已经存储了sel-imp的的个数)

为什么是在 3/4 时进行扩容

在哈希这种数据结构里面,有一个概念用来表示空位的多少叫做装载因子——装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降

负载因子是3/4的时候,空间利用率比较高,而且避免了相当多的Hash冲突,提升了空间效率

bucket数据为什么会有丢失的情况?

原因是在扩容时,是将原有的内存全部清除了,再重新申请了内存导致的

方法缓存是否有序?

因为sel-imp的存储是通过哈希算法计算下标的,其计算的下标有可能已经存储了sel,所以又需要通过哈希冲突算法重新计算哈希下标,所以导致下标是随机的,并不是固定的

bucket与mask、capacity、sel、imp的关系

  • 类cls拥有属性cache_t,cache_t中的buckets有多个bucket——存储着方法实现imp和方法编号sel强转成的key值cache_key_t

  • mask对于bucket来说,主要是用来在缓存查找时的哈希算法

  • capacity则可以获取到cache_t中bucket的数量

capacity与occupied有何区别?

区别在于一个表示总容量,一个表示实际已使用数量

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

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

相关文章

java面试题:List如何排序?内存溢出/OOM怎么回事?如何排查和解决?

List如何排序 List排序可以通过实现Comparable接口并且实现compareTo方法&#xff0c;或者传入comparator去实现排序。 内存溢出/OOM是怎么回事&#xff1f; 内存溢出就是程序在运行的过程中&#xff0c;申请的内存超过了最大内存限制&#xff0c;导致JVM抛出OOM异常&#x…

Python cryptography【密码库】库功能与使用指南

边写代码零食不停口 盼盼麦香鸡味块 、卡乐比&#xff08;Calbee&#xff09;薯条三兄弟 独立小包、好时kisses多口味巧克力糖、老金磨方【黑金系列】黑芝麻丸 边写代码边贴面膜 事业美丽两不误 DR. YS 野森博士【AOUFSE/澳芙雪特证】377专研美白淡斑面膜组合 优惠劵 别光顾写…

第二十四章 流程控制_ if分支

第二十四章 流程控制: if分支和输入 正如许多编程语言一样Shell也有自己的条件分支语句。有时需要根据情况进行相应的处理&#xff0c;因此可以通过条件分支语句实现&#xff0c;本章主要介绍的是if分支语句。 if语句 在Shell中if语句语法格式如下&#xff1a; if commands…

电脑网络重置,找不到原先自家的WIFI,手机还能正常连接并上网

问题排查&#xff1a;1、电脑感觉网络太慢&#xff0c;因此打算点击了网络重置 2、点击提示会删除网络&#xff0c;在五分钟后关机重启 3、从设备管理器设备的无线wifi属性-事件中发现删除记录 4、选择更新驱动程序 5、从列表中选取 6、更改回老驱动版本 备选方案&#…

C语言_预处理详解

1. 预定义符号 C语言设置了一些预定义符号&#xff0c;可以直接使用&#xff0c;预定义符号也是在预处理期间处理的 1 __FILE__ //进行编译的源文件 2 __LINE__//文件当前的行号 3 __DATE__ //文件被编译的日期 4 __TIME__//文件被编译的时间 5 __STDC__//如果编译器遵循ANSI…

【QT】使用QT帮助手册找控件样式

选择帮助—》输入stylesheet(小写)—》选择stylesheet—》右侧选择Qt Style Sheets Reference 2.使用CtrlF—》输入要搜索的控件—》点击Customizing QScrollBar 3.显示参考样式表–》即可放入QT-designer的样式表中

SQL知识合集(二):函数篇

TRIM函数 作用&#xff1a;去掉字符串前后的空格 SELECT * FROM your_table_name WHERE TRIM(column_name) ; COALESCE函数 作用&#xff1a;返回其参数中的第一个非 NULL 值。它可以接受多个参数&#xff0c;并从左到右依次评估这些参数&#xff0c;直到找到第一个非 NUL…

Cursor 工具项目构建指南: Uniapp Miniprogram 环境下的 Prompt Rules 约束

简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 Cursor 工具项目构建指南: Uniapp Miniprogram 环境下的 Prompt Rules 约束前言项目简…

Java转Go日记(六十):gin其他常用知识

1. 日志文件 package mainimport ("io""os""github.com/gin-gonic/gin" )func main() {gin.DisableConsoleColor()// Logging to a file.f, _ : os.Create("gin.log")gin.DefaultWriter io.MultiWriter(f)// 如果需要同时将日志写入…

cocos单例工厂和自动装配

cocos单例工厂和自动装配 1 单例工厂 1.1 分析 实例字典 原理很简单&#xff0c;只是一个map&#xff0c;确保每个类只保留一个实例&#xff1b; private static _instances new Map<string, any>();获取与存储实例 这边使用的方式是生成一个唯一的id存储在类上&…

django paramiko 跳转登录

在使用Django框架结合Paramiko进行SSH远程操作时&#xff0c;通常涉及到自动化脚本的执行&#xff0c;比如远程服务器上的命令执行、文件传输等。如果你的需求是“跳转登录”&#xff0c;即在登录远程服务器后&#xff0c;再通过该服务器的SSH连接跳转到另一台服务器&#xff0…

《C++初阶之类和对象》【命名空间 + 输入输出 + 缺省参数 + 函数重载】

【命名空间 输入&输出 缺省参数 函数重载】目录 前言&#xff1a;---------------hello world---------------比较C语言和C的第一个程序&#xff1a;hello word ---------------命名空间---------------什么是命名空间&#xff1f;怎么使用命名空间&#xff1f;怎么定义…

[USACO1.5] 八皇后 Checker Challenge Java

import java.util.*;public class Main {// 标记 对角线1&#xff0c;对角线2&#xff0c;所在x轴 是否存在棋子static boolean[] d1 new boolean[100], d2 new boolean[100], d new boolean[100]; static int n, ans 0;static int[] arr new int[14]; // 记录一轮棋子位置…

云服务器Xshell登录拒绝访问排查

根据你的描述&#xff0c;使用Xshell 8登录云服务器时显示“拒绝访问”&#xff0c;可能涉及多个原因。以下结合搜索结果整理出排查和解决方法&#xff0c;按优先级排序&#xff1a; 一、检查基础网络与端口连通性 本地网络与服务器IP是否可达 在本地电脑的CMD中执行 ping 服务…

Python爬虫实战:研究urlunparse函数相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上的数据量呈现出指数级增长。如何从海量的网页数据中高效地获取有价值的信息,成为了学术界和工业界共同关注的问题。网络爬虫作为一种自动获取网页内容的技术,能够按照预定的规则遍历互联网上的网页,并提取出所需…

Spring AI学习一

随着Chatpt的火爆&#xff0c;现在Spring官方也开始支持AI了并推出了Spring AI框架&#xff0c;目前还没发布正式版本&#xff0c;这里可以先看一下官方依赖的版本。 Spring官网地址可以看这里&#xff1a;Spring | Home 目前官网上是有这两个版本&#xff1a;1.0.0和1.1.0-SN…

reverse笔记

一&#xff0c;strcat的使用方法&#xff08;在攻防世界中刷题时遇到的&#xff09; 二&#xff0c;壳&#xff08;做题遇到过但是一直不是很理解&#xff0c;今天查了一下&#xff09; 壳是一种软件保护技术&#xff0c;能够防止程序被轻易地分析和修改。 总而言之&#xff0…

spring4第7-8课-AOP的5种通知类型+切点定义详解+执行顺序

继续学习&#xff0c;方便自己复查记录 ①AOP简介&#xff1a; 面向切面编程(也叫面向方面编程)&#xff1a;Aspect Oriented Programming(AOP)。 Spring框架中的一个重要内容。。 通过预编译方式和运行期间动态代理实现在不修改源代码的情况下给程序动态统一添加功能…

EscapeX:去中心化游戏,开启极限娱乐新体验

VEX 平台推出全新去中心化游戏 EscapeX&#xff08;数字逃脫&#xff09;&#xff0c;创新性地将大逃杀玩法与区块链技术相融合。用户不仅能畅享紧张刺激的解谜过程&#xff0c;更能在去中心化、公正透明的环境中参与游戏。EscapeX 的上线&#xff0c;为 VEX 生态注入全新活力&…

Multi Agents Collaboration OS:Web DeepSearch System

背景&#xff1a;多智能体协作驱动网络信息处理的范式革新 随着大型语言模型&#xff08;LLM&#xff09;能力的突破性进展&#xff0c;人工智能正从“单点赋能”向“系统协同”演进。传统单一智能体在复杂业务场景中逐渐显露局限&#xff1a;面对需多维度知识整合、动态任务拆…