【iOS】ARC 与 Autorelease
ARC 与 Autorelease
文章目录
- ARC 与 Autorelease
- 前言
- 何为ARC
- 内存管理考虑方式
- 自己生成的对象,自己持有
- 非自己生成的对象,自己也可以持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
- ARC的具体实现
- 编译期和运行期ARC做的事情
- ARC实现:
- __autoreleasing 与 AutoreleasePool
- AutoreleasePool的结构
- AutoreleasePoolPage
- objc_autoreleasePoolPush
- autoreleaseNewPage
- 压栈对象 autoreleaseFast
- objc_autoreleasePoolPop
- 小结
前言
今天笔者来学习一下有关于ARC和我们这里的一个Auorelease的内容
何为ARC
首先ARC就是我们自动引用计数,自动引用计数主要在代码中插入执行下面步骤
- 生成对象
- 持有对象
- 释放对象
- 废弃对象
在OC中对应的方法是:
对象操作 | OC方法 |
---|---|
生成并且持有对象 | alloc/new/copy/mutableCopy等方法 |
持有对象 | retain方法 |
释放对象 | release |
废弃对象 | dealloc |
内存管理考虑方式
这里我们如果过度注意于引用计数这几个字上面的话,其实不算是一个正常客观的一个思考方式:
- 自己生成的对象,自己持有
- 非自己生成的对象,自己也可以持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
在结合上面的对应的对象操作,我们下面对于这个几个部分进行一个讲解
自己生成的对象,自己持有
使用下面方法名称开头的方法名意味着自己生成的对象只有自己可以持有
- alloc
- new
- copy
- mutablecopy
+ (id)allocMyObject{return [[NSObject alloc] init];
}
+ (id)myObject{return [[NSObject alloc] init];
}
这里我们打一个断点,来看一下这里的内容的内容,这里我们可以看到objc_release
设置了一个标记位.只有用alloc开头的地方做了一个标记,另一个方法就没有标记.
这里博文后面在详细ARC对应实现内容,这里
非自己生成的对象,自己也可以持有
类似于这种代码:
NSMutableArray* ary = [NSMutableArray array];
[ary reatin];
这里是采用reatin
来持有的
不再需要自己持有的对象时释放
[obj release];
非自己持有的对象无法释放
ARC的具体实现
在现在的OC语言中,我们有这些对象的是符合条件的:
- block
- 对象
- 由attribute((NSObject))标记的类型。
编译期和运行期ARC做的事情
- 在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
- ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。
ARC实现:
上面我们介绍了他的消息发送的标记,也就是ARC中生成并持有的操作
+ (id)allocMyObject{return [[NSObject alloc] init];
}
+ (id)myObject{return [[NSObject alloc] init];
}
这里说明我们的allocMyObject
方法会在标识位调用一个objc_release
,而另一个方法则是调用我们的objc_unsfaleClaimAutoreleaseReturnVlaue
.
这里还要对比一个函数objc_autoreleaseReturnValue
:这个函数的作用相当于代替我们手动调用 autorelease, 创建了一个autorelease对象。编译器会检测之后的代码, 根据返回的对象是否执行 retain操作, 来设置全局数据结构中的一个标志位, 来决定是否会执行 autorelease操作。该标记有两个状态, ReturnAtPlus0代表执行 autorelease, 以及ReturnAtPlus1代表不执行 autorelease。
id
objc_autoreleaseReturnValue(id obj)
{if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;return objc_autorelease(obj);
}
objc_unsafeClaimAutoreleasedReturnValue
:这个函数的作用是:函数作用是对autorelease对象不做处理仅仅返回,对非autorelease对象调用objc_release函数并返回。所以本情景中它创建时执行了 autorelease操作了,就不会对其进行 release操作了。只是返回了对象,在合适的实际autoreleasepool会对其进行释放的。
id
objc_unsafeClaimAutoreleasedReturnValue(id obj)
{if (acceptOptimizedReturn() == ReturnAtPlus0) return obj;return objc_releaseAndReturn(obj);
}
这时候我们在主函数赋值:
id tmp1 = [self allocMyObject];
这里我们可以看到下面有一个objc_storeStrong
void
objc_storeStrong(id *location, id obj)
{id prev = *location;if (obj == prev) {return;}objc_retain(obj);*location = obj;objc_release(prev);
}
先获取首先获取旧对象,然后进行比较,如果新对象和旧对象相同,则返回。否则,保留新对象,并将新对象的引用+1。否则,保留新对象,并将新对象的引用+1。然后更新指针*location,指向新对象。最后释放旧对象
这里其实就是我们的代码__strong修饰符
给它插入了这个函数objc_storeStrong
,所以在ARC的规则下其实就是通过下面这几种所有权修饰符号来插入不同的内存管理函数进行一个自动内存管理的:
- __strong
- __weak
- __ unsafe __ retain
- __autoreleasing
__autoreleasing 与 AutoreleasePool
在ARC无效的时候:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
在ARC有效的时候:
@autoreleasepool {// Setup code that might create autoreleased objects goes here.id __autoreleasing obj = [[NSObject alloc] init];}
这里通过给对象赋值给附有__autoreleasing的变量等价于在MRC情况下,调用对象的autorelease
方法,就是把对象注册到我们的autorelasePool
如上图所示的,其实可以按照下面这个表来理解__autoreleasing
的和自动释放池的一个关系:
机制 | 角色 |
---|---|
__autoreleasing | 标记对象:声明对象应交给自动释放池管理。 |
Autorelease Pool | 托管对象:存储被标记的对象,并在自身销毁时统一释放它们。 |
但是我们在实际开发中很少使用过有关于__autoreleasing
这个来显式声明,这里有下面几种情况对象会被自动注册到AutoreleasePool
- 编译器会进行优化,检查方法名是否以 alloc/new/copy/mutableCopy开始,如果不是则自动将返回对象注册到 Autoreleasepool;
+ (id)myObject{return [[NSObject alloc] init];
}
-
在访问__weak变量的时候,实际上必定要访问注册到 Autoreleasepool的对象,即会自动加入 Autoreleasepool;
-
id的指针或对象的指针(id*,NSError **),在没有显式地指定修饰符时候,会被默认附加上 __autoreleasing修饰符,加入 Autoreleasepool。这里是为了实现一个传递指针值的安全,把它注册到
autoreleasepool
可以保证这个对象不会被以外释放
AutoreleasePool的结构
int main(int argc, const char * argv[]) {@autoreleasepool {}return 0;
}int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; }return 0;
}
这里可以看到它对应的是这样一个__AtAutoreleasePool
这个结构体:
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);struct __AtAutoreleasePool {__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}void * atautoreleasepoolobj;
};
这里可以看到它的一个结构体样式,从本质上来讲这个释放池也是一个对象.
认识他的一个底层结构:
Autorelease pool implementation- A thread's autorelease pool is a stack of pointers.
线程的自动释放池是指针的堆栈- Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
每个指针都是要释放的对象,或者是POOL_BOUNDARY,它是自动释放池的边界。- A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
池令牌是指向该池的POOL_BOUNDARY的指针。弹出池后,将释放比哨点更热的每个对象。- The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.
堆栈分为两个双向链接的页面列表。根据需要添加和删除页面。- Thread-local storage points to the hot page, where newly autoreleased objects are stored.
线程本地存储指向热页面,该页面存储新自动释放的对象。
可以总结成下面四点:
- 1、自动释放池一个关于指针的栈
- 2、其中的指针是指要
释放的对象
或者pool_boundary
哨兵(现在经常被称为边界
) - 3、自动释放池是一个
页
的结构(虚拟内存中提及过) ,而且这个页是一个双向链表
(表示有父节点 和 子节点,在类中提及过,即类的继承链) - 4、自动释放池和
线程
有关系
我们主要关心三个问题:
- 什么时候创建
- 对象是怎么加入自动释放池的
- 那些对象会被加入
AutoreleasePoolPage
这两个函数是我们之前在上面看到的两个方法:
void *
_objc_autoreleasePoolPush(void)
{return objc_autoreleasePoolPush();
}void
_objc_autoreleasePoolPop(void *ctxt)
{objc_autoreleasePoolPop(ctxt);
}
- 下面这个源码展示对应的一个结构:
#define PAGE_MIN_SHIFT 12
#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
class AutoreleasePoolPage : private AutoreleasePoolPageData
{friend struct thread_data_t;public: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 4096字节大小
#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;// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is // pushed and it has never contained any objects. This saves memory // when the top level (i.e. libdispatch) pushes and pops pools but // never uses them.
# define EMPTY_POOL_PLACEHOLDER ((id*)1)# define POOL_BOUNDARY nil// SIZE-sizeof(*this) bytes of contents follow
从上面的page的信息可以看出,其实每一个自动释放池是一个页,页的大小是4096字节.
然后发现它继承于AutoreleasePoolPageData
,下面展示出这个结构体的样式
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{//用来校验AutoreleasePoolPage的结构是否完整magic_t const magic;//16个字节//指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()__unsafe_unretained id *next;//8字节 存储指针的一个指针//指向当前线程pthread_t const thread;//8字节//指向父节点,第一个结点的parent值为nilAutoreleasePoolPage * const parent;//8字节//指向子节点,最后一个结点的child值为nilAutoreleasePoolPage *child;//8字节//表示深度,从0开始,往后递增1uint32_t const depth;//4字节//表示high water mark 最大入栈数量标记uint32_t hiwat;//4字节//初始化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){}
};
这里画出了他的一个样式,它是一个双方向链表,它其实是一个这样的结构AutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage
中间存储他的一个信息.
objc_autoreleasePoolPush
现在我们学习它的压栈这个函数:
static inline void *push() {id *dest;if (slowpath(DebugPoolAllocation)) { //判断是否有pool// Each autorelease pool starts on a new pool page.//如果没有就创建dest = autoreleaseNewPage(POOL_BOUNDARY);} else {//存在压栈一个哨兵dest = autoreleaseFast(POOL_BOUNDARY);}ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);return dest;}
autoreleaseNewPage
创建一个NewPage
id *autoreleaseNewPage(id obj){//获得当前操作页AutoreleasePoolPage *page = hotPage();//如果存在,则需要压栈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 ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;if (result) result->fastcheck();return result;}
//创建类的函数
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;//判断是否是空占位符,如果是,则压栈哨兵标识符置为YESif (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,则报错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;}//对象是哨兵对象,且没有申请自动释放池内存,则设置一个空占位符存储在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.//压栈这里的哨兵节点if (pushExtraBoundary) {page->add(POOL_BOUNDARY);}// Push the requested object or pool.return page->add(obj);}static __attribute__((noinline))id *autoreleaseNewPage(id obj){AutoreleasePoolPage *page = hotPage();if (page) return autoreleaseFullPage(obj, page);else return autoreleaseNoPage(obj);}
下面笔者直接给出结论:
- 在
autoreleasePool
这里的链表的第一个页面可以存储了504个NSobject
对象的指针,他的大小是4040 + 一个AutoreleasePoolPageData的大小(56字节) = 4096
, 因为这里有一个哨兵节点, - 从第二页开始可以存储505个对象,因为少了一个哨兵节点所以正好可以存储
505
个对象4040 / 8 = 505
(这里为什么存储的是8个字节大小,是因为这里保存的是一个对象的指针,可以更好的利用内存).
下面看一下这个具体的结构
AutoreleasePoolPage中拥有 parent和 child指针,分别指向上一个和下一个 page;当前一个 page的空间被占满(每个 AutorelePoolPage的大小为4096字节)时,就会新建一个 AutorelePoolPage对象并连接到链表中,后来的 Autorelease对象也会添加到新的 page中;
每一个页内类似与一个栈的结构,通过数组实现的一个栈
另外,当 next==begin()时,表示 AutoreleasePoolPage为空;
当 next ==end(),表示 AutoreleasePoolPage已满。
上面这个图展示出了这个双向链表的一个具体结构
压栈对象 autoreleaseFast
上面介绍了有关于创建一个autoreleasePool
所做的事情,下面介绍一下有关于对象是怎么被压入栈中的.
static inline id *autoreleaseFast(id obj){AutoreleasePoolPage *page = hotPage(); // 先获取当前页if (page && !page->full()) { //页没满直接添加return page->add(obj);} else if (page) { //页满了return autoreleaseFullPage(obj, page);} else {return autoreleaseNoPage(obj); //第一次创建页}}
这里也就分成这几个步骤:
- 获取当前操作页,并判断页是否存在以及是否满了
- 如果页
存在,且未满
,则通过add
方法压栈对象 (给next加加,添加数据类似与一个数组的样式); - 如果页
存在,且满了
,则通过autoreleaseFullPage
方法安排新的页面 (就是通过双向链表产生一个新页来实现) - 如果
页不存在
,则通过autoreleaseNoPage
方法创建新页
objc_autoreleasePoolPop
这里的出出栈的思路其实大致和压入栈中一样
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.page = coldPage();token = page->begin();} else {page = pageForPointer(token); //获取当前页}stop = (id *)token;//判断最后一个位置是不是哨兵if (*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);}
- 这里先处理入参
- 容错处理
- 通过
popPage
出栈页 (这里其实就是类似于给这个页中的对象发送release
消息)下面看一下这里的源码
popPage(void *token, AutoreleasePoolPage *page, id *stop){if (allowDebug && PrintPoolHiwat) printHiwat();page->releaseUntil(stop);// memory: delete empty childrenif (allowDebug && DebugPoolAllocation && page->empty()) {// special case: delete everything during page-per-pool debuggingAutoreleasePoolPage *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 fullif (page->lessThanHalfFull()) {page->child->kill();}else if (page->child->child) {page->child->child->kill();}}}
//释放到stop位置之前的所有对象
void releaseUntil(id *stop) {// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbagewhile (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 itwhile (page->empty()) {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) {
#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}
上面其实就展示了这里的一个移除数据的操作:通过id obj = *--page->next; //类似于处栈的方式处理里面的数据
然后发送release
消息来释放内存
小结
这里其实就分成两个部分分析了AutoreleasePool
的内容
- 在自动释放池的压栈(即push)操作中
- 当没有pool,即只有空占位符(存储在tls中)时,则创建页,
压栈哨兵对象
- 在页中
压栈普通对象
主要是通过next
指针递增
进行的, - 当
页满
了时,需要设置页的child
对象为新建页
- 当没有pool,即只有空占位符(存储在tls中)时,则创建页,
- 在出栈操作中
- 通过next指针递减来实现一个释放
- 当
页空
了时,需要赋值页的parent
对象为当前页
相关文章:

【iOS】ARC 与 Autorelease
ARC 与 Autorelease 文章目录 ARC 与 Autorelease前言何为ARC内存管理考虑方式自己生成的对象,自己持有非自己生成的对象,自己也可以持有不再需要自己持有的对象时释放非自己持有的对象无法释放 ARC的具体实现编译期和运行期ARC做的事情ARC实现: __autoreleasing 与 Autoreleas…...
人工智能在智能零售中的创新应用与未来趋势
随着电子商务的蓬勃发展和消费者需求的不断变化,零售行业正面临着前所未有的挑战和机遇。智能零售作为零售行业的重要发展方向,通过引入人工智能(AI)、物联网(IoT)、大数据和云计算等前沿技术,正…...
业务材料——半导体行业MES系统核心功能工业协议AI赋能
一、前置概念 半导体行业 半导体行业主要生产基于半导体材料(如硅、锗、化合物半导体等)的电子元器件及相关产品,广泛应用于计算、通信、能源、医疗等领域。 MES系统 MES系统(Manufacturing Execution System,制造…...
docker部署命令行 — 启动一个 MySQL 数据库服务 并且把它的数据存储挂载到卷(volume)里
挂载卷的配置写法: version: "3" services:db:image: mysqlvolumes:- mysql_data:/var/lib/mysqlvolumes:mysql_data:这段 docker-compose.yml 配置非常典型,是用来启动一个 MySQL 数据库服务 并且把它的数据存储挂载到卷(volume&…...

铁电液晶破局 VR/AR:10000PPI 重构元宇宙显示体验
一、VR/AR 沉浸感困境:传统显示技术的天花板在哪? (一)纱窗效应与眩晕感:近眼显示的双重枷锁 当用户戴上 VR 头显,眼前像素网格形成的 “纱窗效应” 瞬间打破沉浸感。传统液晶 500-600PPI 的像素密度&…...
2025年微信小程序开发:AR/VR与电商的最新案例
引言 微信小程序自2017年推出以来,已成为中国移动互联网生态的核心组成部分。根据最新数据,截至2025年,微信小程序的日活跃用户超过4.5亿,总数超过430万,覆盖电商、社交、线下服务等多个领域(WeChat Mini …...
从零开始,学会上传,更新,维护github仓库
以下是一份从头到尾、覆盖安装、配置、创建仓库、上传项目到 GitHub 的完整教程。全程使用通用示例,不包含任何具体的仓库链接,仅供参考。 一、准备工作 1. 注册 GitHub 账号 打开浏览器,访问 GitHub 官网(输入 “GitHub” 即可找…...
#STM32 HAL库实现的STM32F407时钟配置程序以及和STM32F103配置对比
以下是使用STM32 HAL库实现的STM32F407时钟配置完整代码(基于8MHz外部晶振,配置为168MHz系统时钟),包含详细注释和关键点说明: 完整HAL库实现(system_stm32f4xx.c main.c) 1. 首先在stm32f4xx…...

竞争加剧,美团的战略升维:反内卷、科技与全球化
5月26日,美团发布2025年第一季度业绩报告,交出了一份兼具韧性与创新性的成绩单。 报告显示,公司一季度总营收866亿元,同比增长18%;核心本地商业收入643亿元,同比增长18%;季度研发投入58亿元&a…...

(17)课36:窗口函数的例题:例三登录时间与连续三天登录,例四球员的进球时刻连续进球。
(89)例三登录时间 : 保留代码版本 : CREATE TABLE sql_8( user_id varchar(2), login_date date ); insert into sql_8(user_id,login_date) values(A,2024-09-02),(A,2024-09-03),(A,2024-09-04),(B,2023-11-25),(B,2023-12- 3…...

高性能分布式消息队列系统(二)
上一篇博客将C进行实现消息队列的用到的核心技术以及环境配置进行了详细的说明,这一篇博客进行记录消息队列进行实现的核心模块的设计 五、项目的需求分析 5.1、项目框架的概念性理解 5.1.1、消息队列的设计和生产消费者模型的关系 在现代系统架构中,…...
Spring 官方推荐构造函数注入
1. 依赖关系明确 构造函数注入可以清晰地声明类的依赖关系,所有必需的依赖项都通过构造函数参数传递,使得代码的可读性更高。这种方式让类的使用者能够直观地了解类的依赖,而不需要通过注解或反射来猜测。 2. 增强代码健壮性 构造函数注入…...

华为OD机试真题——天然蓄水库(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
2025 A卷 200分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《天然蓄水库》: 目录 题目…...

【Harmony OS】数据存储
目录 数据存储概述 首选项数据存储 关系型数据库 数据存储概述 • 数据存储 是为了解决应用数据持久化问题,使得数据能够存储在外存中,达到保存或共享目的。 • 鸿蒙应用数据存储包括 本地数据存储 和 分布式数据存储 。 • 本地数据存储 为应用…...

MybatisPlus--核心功能--service接口
Service接口 基本用法 MyBatisPlus同时也提供了service接口,继承后一些基础的增删改查的service代码,也不需要去书写。 接口名为Iservice,而Iservice也继承了IRepository,这里提供的方法跟BaseMapper相比只多不少,整…...

uniapp调试,设置默认展示的toolbar内容
uniapp调试,设置默认展示的toolbar内容 设置pages.json中 pages数组中 json的顺序就可以只需要调整顺序,不会影响该bar在页面中的显示默认展示第一条page...

笔记本电脑开机无线网卡自动禁用问题
1.问题环境 电脑品牌:华硕笔记本天选4 电脑型号:FX507VV 电脑系统:windows 11_x64_24h2 文档编写时间:2025年6月 2.问题现象 1. 笔记本电脑开机之后自动禁用无线网卡 使用USB转RJ45转接头同样无效,这个网卡也给禁…...

推荐一款使用html开发桌面应用的工具——mixone
简介 mixone是开发桌面应用(Win、Mac、Linux)的一款工具、其基于electron实现。其拥有简单的工程结构。以为熟悉前端开发的程序员可以很轻松的开发出桌面应用,它比electron的其他框架更简单,因为那些框架基本上还需要了解electro…...
支持TypeScript并打包为ESM/CommonJS/UMD三种格式的脚手架项目
支持TypeScript并打包为ESM、CommonJS和UMD三种格式的脚手架项目 码云地址 NODE 版本要求 node v16.17.1 npm 8.15.0 设置淘宝镜像 npm set registry https://registry.npmjs.org/ cnpm set registry https://registry.npmjs.org/安装依赖 npm install打包 npm run build…...

【云原生开发】如何通过client-go来操作K8S集群
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...

八.MySQL复合查询
一.基本查询回顾 分组统计 group by 函数作用示例语句说明count(*)统计记录条数select deptno, count(*) from emp group by deptno;每个部门有多少人?sum(sal)某字段求和select deptno, sum(sal) from emp group by deptno;每个部门总工资avg(sal)求平均值select…...
cacti导出的1分钟监控数据csv文件读取并按5分钟求平均值,然后计算95计费值,假设31天的月份
cacti导出的1分钟监控数据csv文件读取并按5分钟求平均值,然后计算95计费值,假设31天的月份 import pandas as pd import openpyxl from openpyxl.styles import Font from openpyxl.utils.dataframe import dataframe_to_rows import os import chardet…...

FastMCP vs MCP:协议标准与实现框架的协同
你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…...

AI视频“入驻”手机,多模态成智能终端的新战场
文|乐乐 今天,无线蓝牙耳机(TWS)已经成为人人都用得起的产品。 但退回到9年前,苹果AirPods是全球第一款真正意义上的无线蓝牙耳机。靠着自研并申请专利的Snoop监听技术,苹果解决了蓝牙耳机左右延时和能耗…...

nginx+tomcat负载均衡群集
一 案例部署Tomcat 目录 一 案例部署Tomcat 1.案例概述 1.1案例前置知识点 (1)Tomcat简介 (2)应用场景 2.实施准备 (1)关闭Linux防火墙 (2)安装Java 2.1 安装配置TOMACT …...
DEEPSEEK帮写的STM32消息流函数,直接可用.已经测试
#include "main.h" #include "MessageBuffer.h"static RingBuffer msgQueue {0};// 初始化队列 void InitQueue(void) {msgQueue.head 0;msgQueue.tail 0;msgQueue.count 0; }// 检查队列状态 type_usart_queue_status GetQueueStatus(void) {if (msgQ…...
day45 python预训练模型
目录 知识点回顾 1. 预训练的概念 2. 常见的分类预训练模型 3. 图像预训练模型的发展史 4. 预训练的策略 5. 预训练代码实战:ResNet18 作业:在 CIFAR-10 上对比 AlexNet 预训练模型 实验结果对比 在深度学习领域,预训练模型已经成为了…...
二维 根据矩阵变换计算缩放比例
在二维空间中,根据矩阵变换计算缩放比例是一个常见的图形学问题。通常,我们通过分析变换矩阵的结构来提取出缩放(Scale)信息。以下是详细的分析和计算方法。 🧮 一、基础:二维变换矩阵结构 在二维仿射变换…...
Vue-Cropper:全面掌握图片裁剪组件
Vue-Cropper 完全学习指南:Vue图片裁剪组件 🎯 什么是 Vue-Cropper? Vue-Cropper 是一个简单易用的Vue图片裁剪组件,支持Vue2和Vue3。它提供了丰富的配置选项和回调方法,可以满足各种图片裁剪需求。 🌟 …...

建造者模式:优雅构建复杂对象
引言 在软件开发中,有时我们需要创建一个由多个部分组成的复杂对象,这些部分可能有不同的变体或配置。如果直接在一个构造函数中设置所有参数,代码会变得难以阅读和维护。当对象构建过程复杂,且需要多个步骤时,我们可…...