【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修改该记录的值…...

程序员前端开发者的AI绘画副业之路:在裁员危机中寻找新机遇
正文: 在这个充满变数的时代,作为一名前端开发者,我经历了行业的起伏,见证了裁员危机和中年失业危机的残酷。在这样的背景下,我开始了利用AI绘画作为副业的探索,不仅为了寻求经济上的稳定,更是为…...

Burp Suite的使用和文件上传漏洞靶场试验
第一步:分析如何利用漏洞,通过对代码的查阅发现,代码的逻辑是先上传后删除,意味着,我可以利用webshell.php文件在上传到删除之间的间隙,执行webshell.php的代码,给上级目录创建一个shell.php木马…...

如何在Ubuntu中安装deepin wine版的企业微信
如何在Ubuntu中安装deepin wine版的企业微信 运行如下一条命令将移植仓库添加到系统中 wget -O- https://deepin-wine.i-m.dev/setup.sh | sh自此以后,你可以像对待普通的软件包一样,使用apt-get系列命令进行各种应用安装、更新和卸载清理了。 安装企业…...

案例:Nginx + Tomcat集群(负载均衡 动静分离)
目录 案例 案例环境 案例步骤 部署Tomcat服务器 部署Nginx服务器 实现负载均衡和读写分离 日志控制 案例 案例环境 操作系统 IP 地址 角色 CentOS 192.168.10.101 Nginx服务器(调度器) CentOS 192.168.10.102 Tomcat服务器① CentOS 1…...
【密码学】密码协议的分类:②认证协议
密码协议的分类有很多种方式,这里我采取的是基于协议实现的目的来分类。可以将密码协议分成三类:认证协议、密钥建立协议、认证密钥建立协议。 一、认证协议是什么? 认证协议都在认证些什么东西呢?认证一般要认证三个东西&#x…...

异步编程(Promise详解)
目录 异步编程 回调函数 回调地狱 Promise 基本概念 Promise的特点 1.Promise是一种构造函数 2.Promise接收函数创建实例 3.Promise对象有三种状态 4.Promise状态转变不可逆 5.Promise 实例创建即执行 6.Promise可注册处理函数 7.Promise支持链式调用 Promise的静…...

DjangoORM注入分享
DjangoORM注入 简介 这篇文章中,分享一些关于django orm相关的技术积累和如果orm注入相关的安全问题讨论。 攻击效果同数据库注入 从Django-Orm开始 开发角度 Django ORM(Object-Relational Mapping)是Django框架中用于处理数…...

【HBZ分享】Redis各种类型的数据结构应用场景
String(字符串类型) 计数器: incr / decr, 比如商品库存,业务号的发号器业务数据key-value缓存, 缓存结果数据,提高网站性能,缓解DB压力分布式session会话, 集群环境下存储token鉴权信息分布式锁ÿ…...

anaconda创建并且配置pytorch(完整版)
📚博客主页:knighthood2001 ✨公众号:认知up吧 ** 🎃知识星球:【认知up吧|成长|副业】介绍** ❤️如遇文章付费,可先看看我公众号中是否发布免费文章❤️ 🙏笔者水平有限,欢迎各位大…...

高级java每日一道面试题-2024年8月10日-网络篇-你对跨域了解多少?
如果有遗漏,评论区告诉我进行补充 面试官: 你对跨域了解多少? 我回答: 跨域问题,即Cross-Origin Resource Sharing(CORS),是现代Web开发中一个非常重要的概念,涉及到浏览器的安全策略——同源策略(Same…...