【iOS】AutoreleasePool自动释放池的实现原理
目录
- ARC与MRC
- 项目中的main函数
- 自动释放池
- @autoreleasepool {}实现原理
- AutoreleasePoolPage
- 总结
- objc_autoreleasePoolPush的源码分析
- autoreleaseNewPage
- autoreleaseFullPage
- autoreleaseNoPage
- autoreleaseFast
- 总结
- autorelease方法源码分析
- objc_autoreleasePoolPop的源码分析
- popPage
- releaseUntil
- 总结
- 通过私有函数打印自动释放池的情况
ARC与MRC
苹果在 iOS 5 中引入了ARC(Automatic Reference Counting)
自动引用计数内存管理技术,通过LLVM
编译器和Runtime
协作来进行自动管理内存
LLVM
编译器会在编译时在合适的地方为 OC 对象插入retain
、release
和autorelease
代码,省去了在MRC(Manual Reference Counting)
手动引用计数下手动插入这些代码的工作,减轻了开发者的工作量
在MRC下,当我们不需要一个对象的时候,要调用release
或autorelease
方法来释放它:
- 调用
release
会立即让对象的引用计数减 1 ,如果此时对象的引用计数为 0,对象就会被销毁
- 调用
autorelease
会将该对象添加进自动释放池中,它会在一个恰当的时刻自动给对象调用release
,所以autorelease
相当于延迟了对象的释放
项目中的main函数
main
函数在整个iOS项目中是一个非常不起眼的函数,却是整个iOS程序的入口
// main.m
#import <UIKit/UIKit.h>
#import "AppDelegate.h"int main(int argc, char * argv[]) {NSString * appDelegateClassName;@autoreleasepool { // 自动释放池appDelegateClassName = NSStringFromClass([AppDelegate class]);}return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
上面main函数的返回值说明,所有的事件、消息全部交给了UIApplication
来处理,意味着整个iOS的应用都是包含在一个自动释放池里的
自动释放池
@autoreleasepool {}实现原理
// main.m
int main(int argc, const char * argv[]) {@autoreleasepool {Person* person = [[[Person alloc] init]];}return 0;
}
我们使用clang
命令将main.m
文件转换为main.cpp
文件,cpp文件内容如下:
int main(int argc, const char * argv[]) {/* @autoreleasepool */{ __AtAutoreleasePool __autoreleasepool; Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")));}return 0;
}
发现会生成一个__AtAutoreleasePool
结构体
struct __AtAutoreleasePool {__AtAutoreleasePool() { // 构造函数,在生成结构体变量的时候调用atautoreleasepoolobj = objc_autoreleasePoolPush();}~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用objc_autoreleasePoolPop(atautoreleasepoolobj);}void * atautoreleasepoolobj;
};
在@autoreleasepool
这个块作用域内:
- 开头声明了一个
__AtAutoreleasePool
结构体类型局部变量,这时会调用构造函数objc_autoreleasePoolPush
- 结尾等作用域结束,局部变量被销毁,调用析构函数
objc_autoreleasePoolPop
看一下push
和pop
的实现:
void *objc_autoreleasePoolPush(void) {// 调用了AutoreleasePoolPage中的push方法return AutoreleasePoolPage::push();
}void objc_autoreleasePoolPop(void *ctxt) {// 调用了AutoreleasePoolPage中的pop方法AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage
分析一下这个核心的类AutoreleasePoolPage
,其本质是AutoreleasePoolPageData
类:
#define PAGE_MIN_SHIFT 12
#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)class AutoreleasePoolPage : private AutoreleasePoolPageData
{friend struct thread_data_t;public:
// 表示一个空池子
# define EMPTY_POOL_PLACEHOLDER ((AutoreleasePoolPage*)1)
// 哨兵对象
# define POOL_BOUNDARY nil// 每页的大小static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOLPAGE_MAX_SIZE; // must be multiple of vm page size
#elsePAGE_MIN_SIZE; // size and alignment, power of 2
#endifprivate:static pthread_key_t const key = AUTORELEASE_POOL_KEY;static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasingstatic size_t const COUNT = SIZE / sizeof(id);static size_t const MAX_FAULTS = 2;// ....
}// AutoreleasePoolPageData
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSstruct AutoreleasePoolEntry {uintptr_t ptr: 48;uintptr_t count: 16;static const uintptr_t maxCount = 65535; // 2^16 - 1};static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endifmagic_t const magic; __unsafe_unretained id *next;pthread_t const thread;AutoreleasePoolPage * const parent; // 指向上一个AutoreleasePoolPage的指针(链表中的第一个为nil)AutoreleasePoolPage *child; // 指向下一个存储AutoreleasePoolPage的指针(链表中的最后一个为nil)// 代表深度,第一个page的depth为0,往后每递增一个page,depth会加1uint32_t const depth;// 表示high water mark(最高水位标记)uint32_t hiwat;AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat): magic(), next(_next), thread(_thread),parent(_parent), child(nil),depth(_depth), hiwat(_hiwat){}
};
PAGE_MIN_SIZE
:从AutoreleasePoolPage类中可以看出,每个page对象的大小为1 << 12
,即2的12次方,4096字节magic
:对当前AutoreleasePoolPage 完整性的校验,就是用来判断对象是否完成初始化的一个标志next
:指向下一个即将产生的autoreleased对象的存放位置(当next == begin()
时,表示AutoreleasePoolPage为空;当next == end()
时,表示AutoreleasePoolPage已满thread
:当前线程,表明page与线程有关child
和parent
:表明每个page对象是通过双向链表联系起来的depth
:代表深度代表深度,第一个page的depth为0,往后每递增一个page,depth会加1hiwat
:表示high water mark(最高水位标记)
总结
@autoreleasepool
底层会生成一个__AtAutoreleasePool
变量,此变量内部又会生成objc_autoreleasePoolPush
和objc_autoreleasePoolPop
两个函数分别在作用域的开始和结尾进行push(入栈)和pop(出栈)操作,这两个操作都是依靠于AutoreleasePoolPage
类的,AutoreleasePoolPage
是一个双向链表结构
- 当进入@autoreleasepool作用域时,objc_autoreleasePoolPush 方法被调用, runtime 会向当前的 AutoreleasePoolPage 中添加一个 nil 对象作为哨兵对象,并返回该哨兵对象的地址;
- 对象调用autorelease方法,会被加入到对应的的AutoreleasePoolPage中去,next指针类似一个游标,不断变化,记录位置。如果加入的对象超出一页的大小,便会自动加一个新页。
- 当离开@autoreleasepool作用域时,objc_autoreleasePoolPop(哨兵对象地址)方法被调用,其会从当前 page 的 next 指标的上一个元素开始查找, 直到最近一个哨兵对象, 依次向这个范围中的对象发送release消息
因为哨兵对象的存在,自动释放池的嵌套也是满足的,不管是嵌套还是被嵌套的自动释放池,找自己对应的哨兵对象就行了
objc_autoreleasePoolPush的源码分析
objc_autoreleasePoolPush -> AutoreleasePoolPage::push
:
// 入栈
static inline void *push()
{id *dest;if (slowpath(DebugPoolAllocation)) {// Each autorelease pool starts on a new pool page.// 创建一个新的page对象,将POOL_BOUNDARY加进去dest = autoreleaseNewPage(POOL_BOUNDARY);} else {// 已有page对象,快速加入POOL_BOUNDARYdest = autoreleaseFast(POOL_BOUNDARY);}ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);return dest;
}
autoreleaseNewPage
如果是一个空池,那么会调用autoreleaseNewPage
:
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{// 获取当前操作页AutoreleasePoolPage *page = hotPage();// 将POOL_BOUNDARY加到page中(入栈)if (page) return autoreleaseFullPage(obj, page);else return autoreleaseNoPage(obj);
}// 获取当前操作页
static inline AutoreleasePoolPage *hotPage()
{// 获取当前页AutoreleasePoolPage *result = (AutoreleasePoolPage *)tls_get_direct(key);// 如果是一个空池,则返回nil,否则,返回当前线程的自动释放池if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;if (result) result->fastcheck();return result;
}
autoreleaseNewPage内部判断有无page,有就调用autoreleaseFullPage
将对象压入栈,否则调用autoreleaseNoPage
创建新的page,然后再进行压栈操作
autoreleaseFullPage
池子中有page,直接入栈
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{// The hot page is full. // Step to the next non-full page, adding a new page if necessary.// Then add the object to that page.ASSERT(page == hotPage());ASSERT(page->full() || DebugPoolAllocation);// 循环遍历当前page是否满了do {// 如果子页面存在,则将页面替换为子页面if (page->child) page = page->child;// 如果子页面不存在,则新建页面else page = new AutoreleasePoolPage(page);} while (page->full());// 设置为当前操作pagesetHotPage(page);// 压入栈return page->add(obj);
}// 设置当前操作页
static inline void setHotPage(AutoreleasePoolPage *page)
{if (page) page->fastcheck();tls_set_direct(key, (void *)page);
}static inline AutoreleasePoolPage *coldPage()
{AutoreleasePoolPage *result = hotPage();if (result) {while (result->parent) {result = result->parent;result->fastcheck();}}return result;
}
add
压栈
id *add(id obj) {assert(!full());unprotect();// 传入对象存储的位置id *ret = next; // faster than `return next-1` because of aliasing// 将obj压栈到next指针位置,然后进行next++,即下一个对象存储的位置*next++ = obj;protect();return ret;
}
autoreleaseNoPage
池子中无page,创建新的page,再入栈
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{// "No page" could mean no pool has been pushed// or an empty placeholder pool has been pushed and has no contents yetASSERT(!hotPage());bool pushExtraBoundary = false;// 判断是否为空占位符,如果是,则将入栈标识为trueif (haveEmptyPoolPlaceholder()) {// We are pushing a second pool over the empty placeholder pool// or pushing the first object into the empty placeholder pool.// Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder.pushExtraBoundary = true;}// 如果不是POOL_BOUNDARY,并且没有pool,则报错else if (obj != POOL_BOUNDARY && DebugMissingPools) {// We are pushing an object with no pool in place, // and no-pool debugging was requested by environment._objc_inform("MISSING POOLS: (%p) Object %p of class %s ""autoreleased with no pool in place - ""just leaking - break on ""objc_autoreleaseNoPool() to debug", objc_thread_self(), (void*)obj, object_getClassName(obj));objc_autoreleaseNoPool(obj);return nil;}// 如果对象是POOL_BOUNDARY,且没有申请自动释放池内存,则设置一个空占位符存储在tls中,其目的是为了节省内存else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {// We are pushing a pool with no pool in place,// and alloc-per-pool debugging was not requested.// Install and return the empty pool placeholder.return setEmptyPoolPlaceholder();}// We are pushing an object or a non-placeholder'd pool.// Install the first page.// 初始化第一页AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);// 设置为当前页setHotPage(page);// Push a boundary on behalf of the previously-placeholder'd pool.// 如果标识为true,则压入栈if (pushExtraBoundary) {page->add(POOL_BOUNDARY);}// Push the requested object or pool.return page->add(obj);
}
autoreleaseFast
如果一开始就有page页面,不是空池子,那么直接进入到autoreleaseFast
,再分别进行判断
static inline id *autoreleaseFast(id obj)
{AutoreleasePoolPage *page = hotPage();if (page && !page->full()) { // 已有page,并且没满return page->add(obj);} else if (page) {// 如果满了,则安排新的pagereturn autoreleaseFullPage(obj, page);} else {// page不存在,新建return autoreleaseNoPage(obj);}
}
总结
- 每一个
AutoreleasePoolPage
对象都会有一定的存储空间,大概占用4096个字节 - 每一个
AutoreleasePoolPage
对象内部的成员变量会占56个字节,然后剩余的空间才用来存储autorelease
对象 - 每一个
@autoreleasePool
的开始都会先将POOL_BOUNDARY
对象压入栈,然后才开始存储autorelease
对象,并且push方法会返回POOL_BOUNDARY
对象的内存地址 - 当一个
AutoreleasePoolPage
对象存满后才会往下一个AutoreleasePoolPage
对象里开始存储 AutoreleasePoolPage
对象里面的begin
和end
分别对应着autorelease对象开始入栈的起始地址和结束地址AutoreleasePoolPage
对象里面的next
指向下一个能存放autorelease对象地址的区域
autorelease方法源码分析
autorelease方法底层会调用objc_object::rootAutorelease()
函数
// objc_object::autorelease
inline id
objc_object::autorelease()
{ASSERT(!isTaggedPointer());if (fastpath(!ISA()->hasCustomRR())) {return rootAutorelease();}return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}// objc_object::rootAutorelease
inline id
objc_object::rootAutorelease()
{// 如果是TaggedPointer就返回if (isTaggedPointer()) return (id)this;if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;return rootAutorelease2();
}// objc_object::rootAutorelease2
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{ASSERT(!isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);
}
最后还是会调用到AutoreleasePoolPage
的autorelease
static inline id autorelease(id obj)
{ASSERT(!obj->isTaggedPointerOrNil());id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#elseASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
#endifreturn obj;
}
然后进入到快速压栈,autoreleaseFast进行压栈操作,autoreleasepool只会将调用了autorelease的对象压入栈
autorelease和objc_autoreleasePush的整体分析如下图所示:
objc_autoreleasePoolPop的源码分析
objc_autoreleasePoolPop -> AutoreleasePoolPage::pop
:
static inline void
pop(void *token)
{AutoreleasePoolPage *page;id *stop;// 判断是否为空占位符if (token == (void*)EMPTY_POOL_PLACEHOLDER) {// Popping the top-level placeholder pool.// 获取当前页page = hotPage();if (!page) {// Pool was never used. Clear the placeholder.// 如果当前页不存在,则清除空占位符return setHotPage(nil);}// Pool was used. Pop its contents normally.// Pool pages remain allocated for re-use as usual.// 如果当前页存在,则将当前页设置为coldPage,token设置为coldPage的开始位置page = coldPage();token = page->begin();} else {// 获取token所在的pagepage = pageForPointer(token);}stop = (id *)token;// 判断最后一个位置,是否是POOL_BOUNDARYif (*stop != POOL_BOUNDARY) {// 如果不是,即最后一个位置是一个对象if (stop == page->begin() && !page->parent) {// Start of coldest page may correctly not be POOL_BOUNDARY:// 1. top-level pool is popped, leaving the cold page in place// 2. an object is autoreleased with no pool// 如果是第一个位置,且没有父节点,什么也不做} else {// Error. For bincompat purposes this is not // fatal in executables built with old SDKs.// 如果是第一个位置,且有父节点,则出现了混乱return badPop(token);}}if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {return popPageDebug(token, page, stop);}// 出栈return popPage<false>(token, page, stop);
}
begin
和end
分别对应着autorelease对象的起始地址和结束地址
// 开始存放autorelease对象的地址:开始地址 + 他本身占用的大小
id * begin() {return (id *) ((uint8_t *)this+sizeof(*this));
}// 结束地址:开始地址 + PAGE_MAX_SIZE
id * end() {return (id *) ((uint8_t *)this+SIZE);
}// coldPage
static inline AutoreleasePoolPage *coldPage()
{AutoreleasePoolPage *result = hotPage();if (result) {while (result->parent) {result = result->parent;result->fastcheck();}}return result;
}
popPage
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{if (allowDebug && PrintPoolHiwat) printHiwat();// 出栈当前操作页面对象page->releaseUntil(stop);// memory: delete empty children// 删除空子项if (allowDebug && DebugPoolAllocation && page->empty()) {// special case: delete everything during page-per-pool debugging// 获取当前页面的父节点AutoreleasePoolPage *parent = page->parent;//删除将当前页面page->kill();// 设置操作页面为父节点页面setHotPage(parent);} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {// special case: delete everything for pop(top)// when debugging missing autorelease poolspage->kill();setHotPage(nil);} else if (page->child) {// hysteresis: keep one empty child if page is more than half full// 如果页面已满一半以上,则保留一个空子级if (page->lessThanHalfFull()) {page->child->kill();}else if (page->child->child) {page->child->child->kill();}}
}// kill
void kill()
{// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbageAutoreleasePoolPage *page = this;while (page->child) page = page->child;AutoreleasePoolPage *deathptr;do {deathptr = page;// 子节点 变成 父节点page = page->parent;if (page) {page->unprotect();//子节点置空page->child = nil;page->protect();}delete deathptr;} while (deathptr != this);
}
内部会调用releaseUntil
循环遍历进行pop操作
releaseUntil
void releaseUntil(id *stop)
{// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage// 循环遍历// 判断下一个对象是否等于stop,如果不等于,则进入while循环while (this->next != stop) {// Restart from hotPage() every time, in case -release // autoreleased more objectsAutoreleasePoolPage *page = hotPage();// fixme I think this `while` can be `if`, but I can't prove it// 如果当前页是空的while (page->empty()) {// 将page赋值为父节点页page = page->parent;// 并设置当前页为父节点页setHotPage(page);}page->unprotect();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSAutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;// create an obj with the zeroed out top byte and release thatid obj = (id)entry->ptr;int count = (int)entry->count; // grab these before memset
#elseid obj = *--page->next;
#endifmemset((void*)page->next, SCRIBBLE, sizeof(*page->next));page->protect();if (obj != POOL_BOUNDARY) { // 只要不是POOL_BOUNDARY,就进行release
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS// release count+1 times since it is count of the additional// autoreleases beyond the first onefor (int i = 0; i < count + 1; i++) {objc_release(obj);}
#elseobjc_release(obj);
#endif}}// 设置当前页setHotPage(this);#if DEBUG// we expect any children to be completely emptyfor (AutoreleasePoolPage *page = child; page; page = page->child) {ASSERT(page->empty());}
#endif
}
总结
pop
函数会将POOL_BOUNDARY
的内存地址传进去- autorelease对象从end的结束地址开始进行发送
release
消息,一直找到POOL_BOUNDARY
为止 - 一旦发现当前页已经空了,就会去上一个页面进行
pop
,并释放当前页面 - 整个入栈出栈的顺序是采用先进后出,和栈中顺序一样,但不代表着这里说的是真正的栈
pop出栈图示:
通过私有函数打印自动释放池的情况
我们可以通过一个私有函数_objc_autoreleasePoolPrint
来打印分析整个autorelease的过程
// 声明内部私有函数,可以调用执行
extern void _objc_autoreleasePoolPrint(void);int main(int argc, const char * argv[]) {@autoreleasepool { // r1 = pushPerson* person1 = [[[Person alloc] init] autorelease];Person* person2 = [[[Person alloc] init] autorelease];@autoreleasepool { // r2 = push()Person* person3 = [[[Person alloc] init] autorelease];@autoreleasepool { // r3 = push()Person* person4 = [[[Person alloc] init] autorelease];_objc_autoreleasePoolPrint();} // pop(r3)} // pop(r2)
// _objc_autoreleasePoolPrint();} // pop(r1)
}return 0;
}
打印结果:
相关文章:

【iOS】AutoreleasePool自动释放池的实现原理
目录 ARC与MRC项目中的main函数自动释放池autoreleasepool {}实现原理AutoreleasePoolPage总结 objc_autoreleasePoolPush的源码分析autoreleaseNewPageautoreleaseFullPageautoreleaseNoPage autoreleaseFast总结 autorelease方法源码分析objc_autoreleasePoolPop的源码分析po…...

stm32—GPIO
0. 引入 在单片机产品中,我们常常可以见到三种模块:LCD灯、KEY按键、BEEP蜂鸣器 LED灯: 一个比较常见的LED电路LED0 ---------- 通过控制LED0引脚(电线) 给它一个低电平(低电压),LED灯就会亮 给它一个高电平(高电压),LED灯就会灭 …...

CocosCreator使用 ProtoBuf WebSocket与服务器对接方法
在 Cocos Creator 中使用 .proto 文件和转换成 TypeScript(TS)两者各有其优缺点,具体选择取决于你的项目需求和团队的开发习惯。以下是两者的一些比较: 1、使用 .proto 文件的优点: 跨语言支持:Protocol B…...
【python基础】while循环语句练习
明显可以感觉到循环比判断要更加难以理解一些,这个就只能通过练习来提高理解和思维能力了。 学习视频:第一阶段-第四章-05-while循环案例-九九乘法表_哔哩哔哩_bilibili 练习一:计算1-10的和 i1#循环的起始值 sum0 while i&l…...

【SpringBoot系列】WebMvcConfigurer配置
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
学懂C++ (十九):高级教程——深入详解C++信号处理
目录 C中的信号处理 1. 信号处理的本质 2. 主要信号类型 3. 核心关键点 4. 经典实例 代码分析 5. 进阶:信号屏蔽与多线程 例子:使用sigaction() 6. Windows中的信号处理 7. 比较与总结 示例:Windows控制台事件处理 总结 C中的信号…...

SOMEIP_ETS_032:echoUINT8ArrayMinSize
测试目的: 确保DUT能够正确处理最小尺寸的UINT8数组参数,并且在发送和接收过程中保持参数值和顺序不变。 描述 本测试用例旨在验证DUT在处理包含最小尺寸UINT8数组参数的SOME/IP消息时,是否能够准确地发送和接收这些参数,确保返…...

JS+CSS案例:可适应上下布局和左右布局的菜单(含二级菜单)
今天,我给大家分享一个原创的CSS菜单,整个菜单全由CSS写成,仅在切换布局时使用JS。合不合意,先看看效果图。 本例图片 接下来,我来详细给大家分享它的制作方法。 文件夹结构 因为涉及到了样式表切换,所以,你需要借鉴一下我的文件夹结构。 CSS文件夹: reset.css 用于…...

【数据结构】线性表,顺序表
一. 线性表 1. 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 2. 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 3. 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理…...

Spring之最基础的创建与Bean的存储与获取(还有Maven加载jar包报错的解决方法)
目录 创建Spring项目 如何解决Maven加载jar包报错 存储Bean和取Bean 在Java中对象也称为Bean。所以后面我提到Bean,大家要知道我说的是什么。 创建Spring项目 我的idea是2022版本的,如果大家和我一样可以参考我的。 1.创建一个Maven项目。图片上忘了…...

RabbitMQ应用问题 - 消息顺序性保证、消息积压问题
文章目录 MQ 消息顺序性保证概述原因分析解决方案基于 spring-cloud-stream 实现分区消费 消息挤压问题概述原因分析解决方案 MQ 消息顺序性保证 概述 a)消息顺序性:消费者消费的消息的顺序 和 生产者发送消息的顺序是一致的. 例如 生产者 发送消息顺序…...
linux tcp通讯demo
linux tcp通讯demo代码。通过用chatgpt生成的代码。做一个代码记录。 一、基本的通讯demo server.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h>…...
在 MongoDB 中,如何配置副本集以实现读写分离?
在 MongoDB 中,配置副本集以实现读写分离主要涉及以下几个步骤: 初始化副本集: 创建副本集时,需要在所有参与节点上运行 rs.initiate() 命令。这将初始化一个新的副本集。 添加成员到副本集: 使用 rs.add() 命令将所有…...

虚拟dom-Diff算法
虚拟dom-Diff算法 vue2 diff算法在vue2中就是patch,通过新旧虚拟dom对比,找到最小变化然后进行dom操作 在页面首次渲染的时候会调用一次patch并创建新的vnode,不会进行深层次的比较,然后再组件中数据发生变化的时候,…...

01创建型设计模式——单例模式
一、单例模式简介 单例模式(Singleton Pattern)是一种创建型设计模式(GoF书中解释创建型设计模式:一种用来处理对象的创建过程的模式),单例模式是其中的一种,它确保一个类只有一个实例ÿ…...

图像分割(一)
一、概述 语义分割:是把每个像素都打上标签(这个像素点是人、树、背景等) 实例分割:不光要区别类别,还要区分类别中的每一个个体 损失函数:逐像素的交叉熵;样本均衡问题 MIOU指标:…...
C++ 新经典:设计模式 目录(先留框架,慢慢来~)
C 新经典:设计模式 C 新经典:设计模式 C 新经典:设计模式第1章 设计模式与软件开发思想、编程环境介绍第2章 模板方法模式第3章 工厂模式、原型模式、建造者模式第4章 策略模式第5章 观察者模式第6章 装饰模式第7章 单件模式第8章 外观模式第…...

go之命令行工具urfave-cli
一、urfave/cli urfave/cli 是一个声明性的、简单、快速且有趣的包,用于用 Go 构建命令行工具。 二、快速使用 2.1 引入依赖 go get github.com/urfave/cli/v2 2.2 demo package mainimport ("fmt""log""os""github.com/ur…...

四种应用层协议——MQTT、CoAP、WebSockets和HTTP——在工业物联网监控系统中的性能比较
目录 摘要(Abstract) 实验设置 实验结果 节选自《A Comparative Analysis of Application Layer Protocols within an Industrial Internet of Things Monitoring System》,作者是 Jurgen Aquilina、Peter Albert Xuereb、Emmanuel Francalanza、Jasmine Mallia …...
MySQL的脏读、不可重复读、幻读与隔离级别
脏读/不可重复读/幻读 脏读 脏读(Dirty Read)发生在一个事务读取了另一个事务尚未提交的数据。如果第二个事务失败并回滚,第一个事务读到的数据就是错误的。这意味着数据从一开始就是不稳定或者“脏”的。 举例 事务A读取了某条记录的值为X。事务B修改该记录的值…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

华为OD机考- 简单的自动曝光/平均像素
import java.util.Arrays; import java.util.Scanner;public class DemoTest4 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint[] arr Array…...

分布式光纤声振传感技术原理与瑞利散射机制解析
分布式光纤传感技术(Distributed Fiber Optic Sensing,简称DFOS)作为近年来迅速发展的新型感知手段,已广泛应用于边界安防、油气管道监测、结构健康诊断、地震探测等领域。其子类技术——分布式光纤声振传感(Distribut…...
OCC笔记:TDF_Label中有多个相同类型属性
注:OCCT版本:7.9.1 TDF_Label中有多个相同类型的属性的方案 OCAF imposes the restriction that only one attribute type may be allocated to one label. It is necessary to take into account the design of the application data tree. For exampl…...