【iOS】AutoreleasePool自动释放池的实现原理

news2024/9/22 15:43:33

目录

    • 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 对象插入retainreleaseautorelease代码,省去了在MRC(Manual Reference Counting)手动引用计数下手动插入这些代码的工作,减轻了开发者的工作量

在MRC下,当我们不需要一个对象的时候,要调用releaseautorelease方法来释放它:

  • 调用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

看一下pushpop的实现:

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_AUTORELEASEPOOL
		PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
		PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif
    
private:
	static pthread_key_t const key = AUTORELEASE_POOL_KEY;
	static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
	static size_t const COUNT = SIZE / sizeof(id);
    static size_t const MAX_FAULTS = 2;
    
    // ....
}

// AutoreleasePoolPageData
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    struct 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!");
#endif

	magic_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会加1
	uint32_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与线程有关
  • childparent:表明每个page对象是通过双向链表联系起来的
  • depth:代表深度代表深度,第一个page的depth为0,往后每递增一个page,depth会加1
  • hiwat:表示high water mark(最高水位标记)
总结

@autoreleasepool底层会生成一个__AtAutoreleasePool变量,此变量内部又会生成objc_autoreleasePoolPushobjc_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_BOUNDARY
       dest = 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());

   // 设置为当前操作page
   setHotPage(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 yet
   ASSERT(!hotPage());

   bool pushExtraBoundary = false;
   
   // 判断是否为空占位符,如果是,则将入栈标识为true
   if (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) {
       // 如果满了,则安排新的page
       return autoreleaseFullPage(obj, page);
   } else {
       // page不存在,新建
       return autoreleaseNoPage(obj);
   }
}
总结
  • 每一个AutoreleasePoolPage对象都会有一定的存储空间,大概占用4096个字节
  • 每一个AutoreleasePoolPage对象内部的成员变量会占56个字节,然后剩余的空间才用来存储autorelease对象
  • 每一个@autoreleasePool的开始都会先将POOL_BOUNDARY对象压入栈,然后才开始存储autorelease对象,并且push方法会返回POOL_BOUNDARY对象的内存地址
  • 当一个AutoreleasePoolPage对象存满后才会往下一个AutoreleasePoolPage对象里开始存储
  • AutoreleasePoolPage对象里面的beginend分别对应着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);
}

最后还是会调用到AutoreleasePoolPageautorelease

static inline id autorelease(id obj)
{
   ASSERT(!obj->isTaggedPointerOrNil());
   id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
   ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#else
   ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
#endif
   return 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所在的page
       page = pageForPointer(token);
   }

   stop = (id *)token;
   // 判断最后一个位置,是否是POOL_BOUNDARY
   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);
}

beginend分别对应着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 pools
       page->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 garbage
   AutoreleasePoolPage *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 objects
       AutoreleasePoolPage *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_PTRS
       AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;

       // create an obj with the zeroed out top byte and release that
       id obj = (id)entry->ptr;
       int count = (int)entry->count;  // grab these before memset
#else
       id obj = *--page->next;
#endif
       memset((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 one
           for (int i = 0; i < count + 1; i++) {
               objc_release(obj);
           }
#else
           objc_release(obj);
#endif
       }
   }

   // 设置当前页
   setHotPage(this);

#if DEBUG
   // we expect any children to be completely empty
   for (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 = push
        Person* 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;
}

打印结果:

在这里插入图片描述

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

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

相关文章

Html详解——Vue基础

HTML是什么&#xff1f; 超文本标记语言&#xff08;英语&#xff1a;HyperText Markup Language&#xff0c;简称&#xff1a;HTML&#xff09;是一种用来结构化 Web 网页及其内容的标记语言。网页内容可以是&#xff1a;一组段落、一个重点信息列表、也可以含有图片和数据表…

山海关古城信息管理测试--片区

1.片区的检验名称编号是否重复 1.1controller添加两个方法&#xff0c;检验片区编号和检验片区名称 作用为&#xff1a;调用方法判断片区编号与片区名称是否重复&#xff0c;并返回返回值 /*** 检验片区编号是否重复*/PostMapping( "/checkPqbhUnique")ResponseBody…

深度解密CRLF注入与重定向漏洞:从原理到实践

在网络安全的世界中&#xff0c;CRLF注入和重定向漏洞常常被视为潜在的威胁&#xff0c;可能导致信息泄露和用户误导等严重后果。CRLF注入利用换行符在HTTP响应中插入恶意代码&#xff0c;而重定向漏洞则可能将用户引导至恶意网站。理解这些漏洞的原理及其复现方法&#xff0c;…

一文了解服务器和电脑主机的区别及各自优势

服务器和电脑主机的区别主要是&#xff1a;服务器专为处理大量数据和网络服务设计&#xff0c;具备高性能、高稳定性和可扩展性&#xff0c;通常用于数据中心或大型企业环境&#xff1b;而电脑主机则面向个人用户&#xff0c;主要用于日常办公、娱乐等通用任务&#xff0c;成本…

【QT】Qt中Websocket的使用

一、WebSocket的定义 WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455&#xff0c;并由RFC7936补充规范。WebSocket API也被W3C定为标准。 WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;…

HelloWorld驱动编写和加载驱动实验

HelloWorld驱动编写和加载驱动实验 Helloworld驱动实验驱动编写驱动的基本框架 内核模块实验设置交叉编译器找到RK3568平台交叉编译器&#xff1a;解压交叉编译器&#xff1a;设置全局的交叉编译器环境验证交叉编译器环境 编写Makefile编译模块模块的加载与卸载查看模块信息 He…

WT2605C蓝牙语音芯片赋能对讲机新体验:无屏操控、音频解码与蓝牙音箱三合一

一、产品概况 对讲机市场是一个技术成熟且具有广泛应用前景的市场。对讲机作为无线通信设备的一种&#xff0c;在许多不同的领域和业务中发挥着重要作用。从技术发展角度来看&#xff0c;对讲机经历了从模拟到数字的转型&#xff0c;以及从简单通信工具向多功能设备的演进。当…

LVS实验——部署DR模式集群

目录 一、实验环境 二、配置 1、LVS 2、router 3、client 4、RS 三、配置策略 四、测试 1.Director服务器采用双IP桥接网络&#xff0c;一个是VPP&#xff0c;一个DIP 2.Web服务器采用和DIP相同的网段和Director连接 3.每个Web服务器配置VIP 4.每个web服务器可以出外网…

【Python机器学习】回归——缩减系数来“理解”数据

如果数据特征比样本点还多&#xff0c;是不可以使用线性回归的&#xff0c;因为在计算的时候会出错。 如果特征比样本点还多&#xff08;n>m&#xff09;&#xff0c;也就是说输入数据的矩阵x不是满秩矩阵。非满秩矩阵在求逆时会出问题。 为了解决上述问题&#xff0c;可以…

贪心算法的初涉(双指针 + “过山车思想”)

“过山车”思想 首先我们用一道力扣的题目&#xff0c;来简单了解一下“过山车思想” 3228. 将 1 移动到末尾的最大操作次数 - 力扣&#xff08;LeetCode&#xff09; 给你一个 二进制字符串 s。 你可以对这个字符串执行 任意次 下述操作&#xff1a; 选择字符串中的任一…

京东京造的C2M供应链模式

京东自有品牌业务于2018年1月正式上线&#xff0c;在京东发展已久&#xff0c;依托京东供应链优势&#xff0c;已搭建出京东京造、惠寻、佳佰等多品牌矩阵。 京东给零售企业释放出了一个讯号&#xff1a;C2M崛起&#xff0c;消费者的需求开始走向多元化和个性化&#xff01; …

徐州市委书记宋乐伟一行莅临非凸科技徐州分公司调研

7月23日&#xff0c;徐州市委书记宋乐伟一行莅临非凸科技徐州分公司调研&#xff0c;详细了解非凸科技数智交易产品的生态体系以及AI算力赋能的实践成果&#xff0c;并就相关工作进行了现场指导与交流。 非凸科技徐州分公司位于淮海路经济区金融服务中心云盛大厦&#xff0c;致…

基于JSP、java、Tomcat三者的项目实战--校园交易平台系统--(实习,答辩皆可用到)--万字爆更

技术支持&#xff1a;JAVA、JSP 服务器&#xff1a;TOMCAT 7.0.86 编程软件&#xff1a;IntelliJ IDEA 2021.1.3 x64 全部文件展示 网页实现功能截图 主页 注册 登录 购物车主页 修改功能 修改成功 添加商品功能 添加成功 添加进入购物车功能 支付功能 支付过的历史清单账单…

Comsol 声固耦合条件下超长水管路声传递损失

声固耦合条件指的是声波在固体和液体之间传递时&#xff0c;两者之间存在接触或耦合的情况。在水管路中&#xff0c;声固耦合条件下的声传递损失可以通过以下几个因素来影响和计算&#xff1a; 1. 声波的反射和透射&#xff1a;当声波从一个介质传递到另一个介质时&#xff0c…

服务器 Linux 的网络信息

博主上回大致讲解了文件系统&#xff0c;今天来说说 Linux 的网络信息&#xff0c;还是比较重要的~ 主机名称 临时修改 hostname node01 永久修改 vi /etc/hostname DNS解析 域名解析服务可以将域名转换为IP地址DNS域名劫持 window --> C:\Windows\System32\drivers…

Java 2.2 - Java 集合

Java 集合&#xff0c;也叫做容器&#xff0c;主要是由两大接口派生而来&#xff1a;一个是 Collection 接口&#xff0c;主要用于存放单一元素&#xff1b;另一个是 Map 接口&#xff0c;主要用于存放键值对。对于 Collection 接口&#xff0c;其下又有三个主要的子接口&#…

七大云安全威胁及其应对方法

关注公众号网络研究观获取更多内容。 对于任何依赖云来容纳快速增长的服务的企业来说&#xff0c;确保安全都是重中之重。然而&#xff0c;正如大多数云采用者很快意识到的那样&#xff0c;迁移到动态云环境需要新的和更新的安全措施&#xff0c;以确保数据和其他关键资产在整…

凯特王妃与戴安娜王妃:有跨越时空的优雅共鸣!

显而易见的是都是王妃,而王妃不仅是称谓也是一个头衔,她们同时要承担这个头衔应尽的职责! 在皇室世界里,总有一些名字,如同璀璨星辰,即便时光流转,依旧熠熠生辉。现在让我们揭开一段不为人知的幕后故事——凯特王妃与已故的戴安娜王妃之间,那些超越时代、共通的优雅与情…

前端模块化-手写mini-vite

前言 本文总结了一些关于 Vite 的工作原理&#xff0c;以及一些实现细节。 本节对应的 demo 可以在这里找到。 什么是 Vite Vite 是一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports&#xff0c;在服务器端按需编译返回&#xff0c;完全跳过了打包这个…

PyTorch深度学习实战(5)—— Tensor的命名张量和基本结构

1. 命名张量 命名张量&#xff08;Named Tensors&#xff09;允许用户将显式名称与Tensor的维度关联起来&#xff0c;便于对Tensor进行其他操作。笔者推荐使用维度的名称进行维度操作&#xff0c;这样可以避免重复计算Tensor每个维度的位置。支持命名张量的工厂函数&#xff08…