iOS内存管理--内存的分区

news2024/11/25 7:38:05

内存分配区域

iOS程序内存分为5个区域
栈区,堆区,BSS,全局变量,代码区
五个区域有两种分配时间

运行时分配:栈区,堆区

栈区:局部变量,函数参数(形式参数),自动分配内存,当局部变量的作用域执行完毕之后就会被系统立即回收,由编译器分配和自动释放,函数执行完后,局部变量和形参占用的空间会自动被释放。效率比较高,但是分配的容量很有限。

堆区:程序代码new出的对象,动态分配内存,alloc出来的对象需要程序员自己进行内存管理

编译时分配:BSS,数据段,代码段

BSS:静态变量,用来存放未初始化的全局变量,静态变量,一旦初始化成功则回收,并转存到数据段中

数据段:已经初始化的全局变量,常量,静态变量,直到程序结束的时候才会被回收

代码段:程序二进制代码,程序结束的时候系统会自动回收储存在代码段中的数据,内存区域较小

栈区的地址一般0x7开头,堆区0x6开头,这两个区域时函数调用的时候分配,函数执行结束后一般会释放。
BSS区域,常量区域内存再运行期间一直存在,直到程序运行结束。

函数的调用过程:

执行某个函数时,如果有参数,则在栈上为形式参数分配空间(如果是引用类型的参数则除外),继续进入到函数体内部,如果遇到变量,则按情况,为变量在不同的存储区域分配空间(如果是static类型的变量,则是在进行编译的过程中已经就分配了空间),函数内的语句执行完后,如果函数没有返回值,则 直接返回调用该函数的地方(即执行原点),如果存在返回值,则先将返回值进行拷贝传回,再返回执行原点,函数全部执行完毕后,进行退栈操作,将刚才函数内 部在栈上申请的内存空间释放掉。

#import "ViewController.h"

 

**@interface** ViewController ()

 

**@end**

 

**static** NSInteger test = 1;

**const** NSString *str = @"hello";

**@implementation** ViewController

 

- (**void**)viewDidLoad {

   [**super** viewDidLoad];

   [**self** testMethod:@"method"];    // Do any additional setup after loading the view.

}

- (**void**)testMethod:(NSString *)param

{

   NSString *local = [[NSString alloc] initWithFormat:@"%@", @"hello world!"];

   NSString *localstr = [[NSString alloc] initWithFormat:@"%@", param];

   //局部变量

   NSInteger tmpInt = 2;

   //堆变量

   NSLog(@"%@", local);

   //taggedPointer

   NSLog(@"%@", localstr);

   //全局变量

   NSLog(@"%@", str);

   //静态变量

   NSLog(@"%ld", test);//断点加在这里,在    右击debug区域的变量,选择View Memory of "xxxx"可以看到这个变量的内存地址。这里有个变量比较特殊,局部变量localstr在debug 区域可以看到它是NSTaggedPointerString,也没有放在堆区。对于这种较短的字符串,苹果做了优化,没有将他们放到堆区,而是直接把值写入到指针中,以便加快访问速度,并减少内存。

}

 

 

**@end**

![[Pasted image 20230714152732.png]]

![[Pasted image 20230714164658.png]]

栈区:

特点:
1、栈是系统数据结构,其对应的进程或者线程是唯一的

2、栈是向低地址扩展的数据结构

3、栈是一块连续的内存区域,遵循先进后出(FILO)原则

4、栈的地址空间在iOS中是以0X7开头

5、栈区一般在运行时分配

存储内容

1、栈区是由编译器自动分配并释放的,主要用来存储局部变量

2、函数的参数,例如函数的隐藏参数(id self,SEL _cmd)

优缺点

优点:因为栈是由编译器自动分配并释放的,不会产生内存碎片,所以快速高效

缺点:栈的内存大小有限制(其实绝大部分情况都不会达到最大值)

iOS主线程栈大小是1MB,其他主线程是512KB,MAC只有8M

注意:传入函数的参数值、函数体内声明的局部变量等,由编译器自动分配释放,通常在函数执行结束后就释放了(不包括static修饰的变量,static意味该变量存放在全局/静态区)。

堆区–heep

特点

1、堆是向高地址扩展的数据结构

2、堆是不连续的内存区域,类似于链表结构(便于增删,不便于查询),

3、遵循先进先出(FIFO)原则

4、堆的地址空间在iOS中是以0x6开头,其空间的分配总是动态的

5、堆区的分配一般是在运行时分配

存储内容

1、堆区是由程序员动态分配和释放的,如果程序员不释放,程序结束后,可能由操作系统回收

2、OC中使用alloc或者使用new开辟空间创建对象

3、C语言中使用malloc、calloc、realloc分配的空间,需要free释放

优缺点

优点:灵活方便,数据适应面广泛

缺点:需手动管理,速度慢、容易产生内存碎片

注意:当需要访问堆中内存时,一般需要先通过对象读取到栈区的指针地址,然后通过指针地址访问堆区。因为现在iOS默认使用ARC来进行内存管理,所以也不需要手动释放。

全局区(静态区)(BSS段)

**BSS段(bss segment)**通常是指用来存放程序中未初始化的或者初始值为0的全局变量的一块内存区域。BSS是Block Started by Symbol的简称。BSS段属于静态内存分配。

**数据段(data segment)**通常是指用来存放程序中已初始化的全局变量的一块内存区域,数据段属于静态内存分配。

全局区是编译时分配的内存空间,在iOS中一般以0x1开头,在程序运行过程中,此内存中的数据一直存在,程序结束后由系统释放,主要存放

1、未初始化的全局变量和静态变量,即BSS区(.bss)

2、已初始化的全局变量和静态变量,即数据区(.data)

由static修饰的变量会成为静态变量,该变量的内存由全局/静态区在编译阶段完成分配,且仅分配一次。

static可以修饰局部变量也可以修饰全局变量。

常量区(数据段)

常量区是编译时分配的内存空间,在iOS中的地址一般以0x1开头,比如:0x100000000011038a,在程序结束后由系统释放

通常是指用来存放程序中已经初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。字符串常量等是放在只读数据段中,结束程序时才会被收回。
常量区编译时分配的内存空间,在程序结束后由系统释放,主要存放:

■ 已经使用了的,且没有指向的字符串常量
■ 字符串常量因为可能在程序中被多次使用,所以在程序运行之前就会提前分配内存

代码区(代码段)

代码区是编译时分配主要用于存放程序运行时的代码,代码会被编译成二进制存进内存

代码区需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。

注:除了以上内存区域外,系统还会保留一些内存区域。

TaggedPointer

简单来说 TaggedPointer 是会将某些类型的短小数据直接存储到 对象指针中 ,而不是存储到指针指向的地址中,也就是说 如果这个对象是 TaggedPointer 对象,则该对象的地址里存储的内容就包含了该对象引用的值。
Tagged Pointer 专门用来存储小对象,例如NSNumber,NSDate等,Tagged Pointer指针的值不再是单纯的地址了,而是真正的值,所以,实际上它也不再是一个对象了,它只是一个披着对象皮的普通变量而已,所以它的内存并不存储在堆区,也不需要malloc和free。这样在读取上有着3倍的效率,创建时候比以前快106倍。

那么哪些类型支持 TaggedPointer 呢?

在 objc-internal.h 文件中有如下定义 ,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

__NSCFConstantString

Constant->常量

通俗理解其就是常量字符串,是一种编译时常量
这种对象存储在字符串常量区。
通过打印起retainCount的值,发现很大,2^64 - 1,测试证明对其进行release操作时,retainCount不会产生任何变化是创建之后便无法释放掉的对象。
当我们使用不同的字符串对象进行创建时当内容相同,其对象的地址也相同,这也就证明了常量字符串是一种单例。
这种对象一般通过字面值 @“…”、CFSTR(“…”) (一种宏定义创建字符串的方法)或者 stringWithString: 方法(现在会报警告⚠️这个方法等同于字面值创建的方法)。

__NSCFString

和 __NSCFConstantString 不同, __NSCFString 对象是在运行时创建的一种 NSString 子类,他并不是一种字符串常量。所以和其他的对象一样在被创建时获得了 1 的引用计数。
这种对象被存储在堆上。
通过 NSString 的 stringWithFormat 等方法创建的 NSString 对象一般都是这种类型。
如果字符串长度大于9或者如果有中文或其他特殊符号(可能是非 ASCII 字符)存在的话则会直接成为 __NSCFString 类型

NSTaggedPointerString

标签指针的概念
理解这个类型,需要明白什么是标签指针,这是苹果在 64 位环境下对 NSString,NSNumber 等对象做的一些优化。

简单来讲可以理解为把指针指向的内容直接放在了指针变量的内存地址中,因为在 64 位环境下指针变量的大小达到了 8 字节足以容纳一些长度较小的内容。于是使用了标签指针这种方式来优化数据的存储方式。

从其的引用计数可以看出,这也是一个释放不掉的单例常量对象。当我们使用不同的字符串对象进行创建时当内容相同,其对象的地址也相同。在运行时根据实际情况创建。
对于 NSString 对象来讲,当非字面值常量的数字,英文字母字符串的长度小于等于 9 的时候会自动成为 NSTaggedPointerString 类型。(小于等于9这个数据也是原博主进行猜测,经过测试在字符串含有q的时候是小于等于7)

在源码中,苹果是怎么判断是否是Tagged Point 呢?是将第63位读取出来判断01吗?其实不是的
很多函数内部都有做判断,笔者整理如下:

1、判断class

2、获取 isa指针的时候

3、设置关联对象的时候会判断

4、自动释放池在释放对象的时候会判断

5、引用计数加减的时候会进行判断

6、对象释放的时候会进行判断(好像有个经典崩溃面试题用到了这个知识点)
在这里插入图片描述
对于一个对象的存储,苹果做了优化,对于isa指针呢
对象的isa指针,用来表明对象所属的类类型

union isa_t {
  isa_t() { }
  isa_t(uintptr_t value) : bits(value) { }

  Class cls;
  uintptr_t bits;
#if defined(ISA_BITFIELD)
  struct {
      ISA_BITFIELD;  // defined in isa.h
  };
#endif
};

在这里插入图片描述
从图中可以看出,我们所谓的isa指针,最后实际上落脚于isa_t的联合类型。那么何为联合类型呢? 联合类型是C语言中的一种类型,是一种n选1的关系,联合的作用在于,用更少的空间,表示了更多的可能的类型,虽然这些类型是不能够共存的。比如isa_t 中包含有cls,bits, struct三个变量,它们的内存空间是重叠的。在实际使用时,仅能够使用它们中的一种,你把它当做cls,就不能当bits访问,你把它当bits,就不能用cls来访问。

对于isa_t联合类型,主要包含了两个构造函数isa_t(),isa_t(uintptr_t value)和三个变量cls,bits,struct,而uintptr_t的定义为typedef unsigned long。

当isa_t作为Class cls使用时,这符合了我们之前一贯的认知:isa是一个指向对象所属Class类型的指针。然而,仅让一个64位的指针表示一个类型,显然不划算。

因此,绝大多数情况下,苹果采用了优化的isa策略,即,isa_t类型并不等同而Class cls, 而是struct。
在这里插入图片描述

// ISA_BITFIELD定义如下
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

具体的内容参考这篇文章,此处不过多赘述[[isa指针]]

散列表、引用计数表

Sidetable主要包含spinlock,引用计数(存放extra_rc接收的另一半引用计数),弱引用表。

truct SideTable {
    spinlock_t slock;
    // 存放从extra_rc接收的那一半引用计数
    RefcountMap refcnts;
    // 弱引用表
    weak_table_t weak_table;
 
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }
 
    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }
 
    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }
 
    // Address-ordered lock discipline for a pair of side tables.
 
    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

1.spinlock_t 加锁

spinlock_t 不是自旋锁,在底层代码查找的过程中,我们可以发现他是一把os_unfair_lock锁,在使用sidetable的时候,频繁的读取需要加锁,一张表无疑影响了效率,因此,我们采用stripedMap来分散压力,且stripedMap的数量是根据系统来确定的(真机模式下sidetable最多为8张,虚拟机等为64张).


// 上面 SideTables 的实现
static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}

2.RefcountMap(引用计数表)

  • RefcountMap本身从DenseMap得来
  • `typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;
    存放从extra_rc接收的那一半引用计数
if (variant == RRVariant::Full) {
    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        // 将引用计数一半存在散列表中的方法
        sidetable_addExtraRC_nolock(RC_HALF);
    }
    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
    ASSERT(!transcribeToSideTable);
    ASSERT(!sideTableLocked);
}

接着,来看一下 sidetable_addExtraRC_nolock 方法:

bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    // 获取SideTables,也就是StripeMap
    SideTable& table = SideTables()[this];
 
    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
 
    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
 
    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}

WeakTable(弱引用表)

弱引用底层调用objc_initWeak:

id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }
 
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

storeWeak:

添加引用的时候调用storeWeak一共有五个参数,其中3个参数定义在了template模版参数中(HaveOld:weak指针是否指向一个弱引用;HavNew:weak指针是否需要指向一个新的引用;crashIfDeallocating表示被弱引用的对象是否正在析构)。
weak_unregister_no_lock:清除原来弱引用表中的数据
weak_register_no_lock:将weak的指针地址添加到对象的弱引用表

enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
 
// HaveOld:weak指针是否指向一个弱引用
// HavNew:weak指针是否需要指向一个新的引用
template <HaveOld haveOld, HaveNew haveNew,
          enum CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);
 
    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
 
    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {//如果有拿到旧表
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {//如果没有创建新表
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
 
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
 
    if (haveOld  &&  *location != oldObj) {//如果旧表不存在对应的obj
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }
 
    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {//有新表和新对象
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) //如果类没有初始化就重新初始化
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);
 
            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;
 
            goto retry;
        }
    }
 
    // Clean up old value, if any.
    if (haveOld) {//如果指针曾经指向别的对象,就清除
        // 清除原来弱引用表中数据
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
 
    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            // 将weak的指针地址添加到对象的弱引用表
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected
 
        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            // 将对象曾经指向过弱引用的标识置为true,没有弱引用的释放更快
            newObj->setWeaklyReferenced_nolock();
        }
 
        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
 
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
 
    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);
 
    return (id)newObj;
}


weak_entry_t:

struct weak_table_t {
    // 弱引用数组
    weak_entry_t *weak_entries;
    // 数组个数
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

weak_entries是一个哈希数组,一个对象可以被多个弱引用指针引用,因此,这里用数组的形式表示一个对象的多个弱引用;数组中存储的内容就是弱引用对象的指针地址。当对象的弱引用个数小于等于4时走静态存储(在weak_entry_t初始化的时候一并分配好),大于4走动态存储。

sidetables总结:

sidetables可以理解为一个全局的hash数组,里面存储了sidetables类型的数据,其中长度为8或者64
一个obj(oc对象)对应了一个sideTable,但是一个SideTable,会对应多个obj,因为sidetabels的数量只有8或者64个,所以有很多obj会共用一个sidetable
在弱引用表中,key是对象的地址,value是weak指针地址的数组(weak_entry_t)
weak_unregister_no_lock 和 weak_register_no_lock 中都是对 weak_entry_t 类型的数组进行操作
_weak修饰对象(不会放入自动释放池),会调用objc_loadWeakRetained;使得引用计数加一,但仅是临时变量;被引用的对象不会增加他的引用计数。
在这里插入图片描述

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

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

相关文章

Hadoop概念学习(无spring集成)

Hadoop 分布式的文件存储系统 三个核心组件 但是现在已经发展到很多组件的s 或者这个图 官网地址: https://hadoop.apache.org 历史 hadoop历史可以看这个: https://zhuanlan.zhihu.com/p/54994736 优点 高可靠性&#xff1a; Hadoop 底层维护多个数据副本&#xff0c;所…

[C初阶]循环和分支语句

目录 if...else语句 ​编辑 易犯错误 打印100以内的奇数 switch...case语句 输出星期 循环语句 对比判断 1.break终止循环 2.continue 读取字符 缓冲区读取 只输出数字字符 for循环 do...while循环 n的阶乘求和 循环实现动态打印 猜数字游戏【总结】 goto ​…

C++的各种用法展示

&#xff43;&#xff0b;&#xff0b;与数学典型算法的结合 阿姆斯特朗数 // A number is called as Armstrong number if sum of cubes of digits of number is // equal to the number itself. // For Example 153 is an Armstrong number because 153 153. #include <…

K8S初级入门系列之十二-计算资源管理

一、前言 K8S集群中着这各类资源&#xff0c;比如计算资源&#xff0c;API资源等&#xff0c;其中最重要的是计算资源&#xff0c;包括CPU&#xff0c;缓存&#xff0c;存储等。管理这些资源就是要在资源创建时进行约束和限制&#xff0c;在运行时监控这些资源的指标&#xff0…

[23] HeadSculpt: Crafting 3D Head Avatars with Text

paper | project 本文主要想解决&#xff1a;1&#xff09;生成图像的不连续问题&#xff1b;2&#xff09;3D修改中的保ID问题。针对第一个问题&#xff0c;本文引入了Landmark-based ControlNet特征图和<back-view>的text embedding&#xff1b;针对第二个问题&#x…

Python 算法基础篇:插入排序和希尔排序

Python 算法基础篇&#xff1a;插入排序和希尔排序 引言 1. 插入排序算法概述2. 插入排序算法实现实例1&#xff1a;插入排序 3. 希尔排序算法概述4. 希尔排序算法实现实例2&#xff1a;希尔排序 5. 插入排序与希尔排序的对比总结 引言 插入排序和希尔排序是两种常用的排序算法…

java后端导出前端展示

效果图 前端代码 exportExcelAll(){window.location.href getBaseUrl() Action/excelDataAll?happenDatethis.params.happenDate;},后端代码 try{Workbook workbooknew XSSFWorkbook();//创建sheetSheet sheet1workbook.createSheet("结果总数拦截记录");//写入…

第一百一十二天学习记录:数据结构与算法基础:循环链表和双向链表以及线性表应用(王卓教学视频)

循环链表 带尾指针循环链表的合并 双向链表 单链表、循环链表和双向链表的时间效率比较 顺序表和链表的比较 链式存储结构的优点 1、结点空间可以动态申请和释放&#xff1b; 2、数据元素的逻辑次序靠结点的指针来指示&#xff0c;插入和删除时不需要移动数据元素。 链式存储…

【pytho】request五种种请求处理为空和非空处理以及上传excel,上传图片处理

一、python中请求处理 request.args获取的是个字典&#xff0c;所以可以通过get方式获取请求参数和值 request.form获取的也是个字典&#xff0c;所以也可以通过get方式获取请求的form参数和值 request.data&#xff0c;使用过JavaScript&#xff0c;api调用方式进行掺入jso…

[数据结构 -- C语言] 二叉树(BinaryTree)

目录 1、树的概念及结构 1.1 树的概念 1.2 树的相关概念&#xff08;很重要&#xff09; 1.3 树的表示 2、二叉树的概念及结构 2.1 概念 2.2 特殊二叉树 2.3 二叉树的性质&#xff08;很重要&#xff09; 2.4 练习题 2.5 二叉树的存储结构 2.5.1 顺序存储 2.5.2 链…

Windows10 下 Neo4j1.5.8 安装教程

前言 Neo4j 是一个高性能的、NOSQL 图形数据库&#xff0c;它将结构化数据存储在网络上而不是表中。基于磁盘的、具备完全的事务特性的 Java 持久化引擎&#xff0c;这里就不把他和常用关系型数据库做对比了。因为篇幅有限&#xff0c;我这里也是第一次使用&#xff0c;所以以…

windows安装cmake快速教程

1、下载cmake cmake官网直直接下载速度都很慢&#xff0c;可以到点击下载地址进行下载。 点击下载地址进去之后&#xff0c;可以看到有很多的版本&#xff0c;这里根据自己的需要选一个版本即可&#xff08;建议不要选择太早的版本&#xff09;&#xff0c;我这里选择的3.22版…

【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)

&#x1f389;欢迎来到Python专栏~与FPGA、蓝牙模块实现串口通信 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;Python学习专栏 文章作者技术和水平有限&#xff0c;如果文中出现错误&#…

基于Java+SpringBoot+vue前后端分离校园周边美食探索分享平台设计实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

SpringCloud nacos 集成 feign 实例

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

【运维工程师学习七】DNS底层原理及搭建自己的DNS服务器

【运维工程师学习七】DNS底层原理及搭建自己的DNS服务器 0、前言1、域名&#xff08;Domain&#xff09;的由来2、域名&#xff08;Domain&#xff09;的级别3、域名&#xff08;Domain&#xff09;的命名规则4、域名&#xff08;Domain&#xff09;的后缀有哪些5、域名&#x…

[QT编程系列-32]:科学计算 - QT支持的科学计算库

目录 第1章 QT中如何支持科学计算&#xff1f; 1.1 QT没有专门的科学计算库 1.2 QT没有专门的数学运算库 1.3 QT没有数字信号DSP处理库 1.4 QT没有类numpy库 1.5 QT支持的数学运算 第2章 QT数学库详解 2.1 QtMath 2.2 QVector 2.3 QMatrix 第1章 QT中如何支持科学计算…

【数据结构】——LRU Cache

这里写目录标题 什么是LRU CacheLRU Cache的实现LRU Cache的OJ 什么是LRU Cache LRU是Least Recently Used的缩写&#xff0c;意思是最近最少使用&#xff0c;它是一种Cache替换算法。 什么是Cache&#xff1f;狭义的Cache指的是位于CPU和主存间的快速RAM&#xff0c; 通常它不…

【Soft NMS】《Soft-NMS – Improving Object Detection With One Line of Code》

ICCV-2017 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method5 Experiments5.1 Results5.2 Sensitivity Analysis5.3 When does Soft-NMS work better?5.4 Qualitative Results 6 Conclusion&#xff08;own&#xff09; 1 Background…

【Android】Ubuntu20.04编译Android 13并用模拟器运行

前言 一直好奇Android系统是怎么定制的&#xff0c;直到亲自走一遍Android系统编译流程才发现并没想象的复杂。 这就跟app开发一样&#xff0c;Google官方其实都提供了平台、文档、IDE及一些工具&#xff0c;咱们只要按照官方提供的指南来操作就行了。 如果Android没有提供这…