Nginx源码分析--内存池

news2025/2/3 19:04:21

1.问题引入

使用C语言编程时,一般使用malloc和free进行动态内存申请和释放。如果一不小心忘记了调用free进行释放,很容易造成内存泄露。另一方面,频繁地进行malloc和free操作,很容易造成内存碎片。与此同时,因为malloc支持多线程同时操作,所以,使用同步锁是不可避免的。当然,根据malloc的实现原理,线程在进行malloc操作的时候,如果不能获得同步锁,就会另外在进程的heap区域开辟一段子区域进行内存申请,这样有效地避免长时间等待。但是频繁尝试去获得锁也需要一定的时间开销。(建议阅读这一篇文章)

2.问题解决

NGINX是一个对性能要求很高的系统。大到架构设计,小到细节实现都对性能提升做了充分的考虑。所以,对应内存管理,除了封装了libc库中的malloc和free操作以外。NGINX也实现了自己的内存管理系统,有效地减少了内存碎片的产生,降低了内存泄露发生的概率,减少了同步操作(尝试)的次数。这些都对NGINX本身性能的提升有一定的帮助。

NGINX自身的内存管理系统最重要的特点就是加强中央集权管理。把内存的申请,释放牢牢地掌握在自己手里。具体来说就是:

NGINX本身维护自己的内存池。当进程申请内存时,先在自身内存池中里去查找,如果找到直接返回。如果所有的内存池都找不到合适的内存,NGINX本身再去向系统去申请一片大内存进行分割管理。这样,有效地减少了系统调用malloc的次数。每次都是相对大片的内存申请,也有效地减少了内存碎片的发生几率。

内存释放进行统一管理。NGINX的内存池提供了大小两种类型的内存片管理。对应小块内存只能在整个内存池销毁时候才能释放。这样可以减少内存泄露的发送几率。

3.数据结构分析

3.1内存池数据块结构

typedef struct {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t            failed;
} ngx_pool_data_t;

结构定义:

  • 指针last 指向当前内存池可用内存的开始地址。下一次申请内存就从last地址开始。
  • 指针end 指向当前内存池的可申请内存的结束地址。指针last和end限定的区域就是我们所谓的小内存块申请的区域。这些小内存块大小可以不同,但是不能超过下面的max数值。这些内存块并没有被有效的管理起来,所以,他们只能在整个内存池释放时才能得到释放。
  • 指针next指向下一个内存池。NGINX的内存池通过next指针进行串联,形成一个内存池链。只有第一个内存池有完整的ngx_pool_t结构,用来维护整个内存池的管理和维护信息。剩余的内存池只有ngx_pool_data_t结构来记录当前内存池的内存申请状态。
  • failed表明当前内存池内存累加申请失败的次数。

3.2内存池头部结构

struct ngx_pool_s {
   2:     ngx_pool_data_t       d;
   3:     size_t                max;
   4:     ngx_pool_t           *current;
   5:     ngx_chain_t          *chain;
   6:     ngx_pool_large_t     *large;
   7:     ngx_pool_cleanup_t   *cleanup;
   8:     ngx_log_t            *log;
   9: };

结构定义:

  • max表明可向内存池申请内存块大小的最大值。如果超过这一值则直接调用malloc向系统申请而不是通过内存池申请。这些通过系统malloc得到的大块内存也要记录在内存池的large单链表字段上,方便进行管理。和小块内存不同,这些大内存块可以通过ngx_pfree进行及时释放。
  • 指针current指向申请开始的内存池,由于内存池是一个链式结构,通过current指针可以避免每次都要遍历内存池节点链表。如果通过一个内存池申请内存失败的次数达到5次,current重新被赋值。这样可以避免每次都要挨个判断每一个内存池是否可以从中申请内存。从而可以节省时间。
  • 指针large用来指向直接向系统申请的大块内存链表(malloc)。
  • 指针chain和指针large类似。不过它指向的是结构ngx_buf_t。chain本身的数据结构ngx_chain_t和它的成员ngx_buf_t都是从内存池中申请的,然后挂入chain这个单向列表中。
  • 指针cleanup是一个函数指针单链表,在内存池释放时被依次调用。其作用类似C++中的析构函数。
  • 指针log指向的函数指针用来记录log时被调用。

4.基本API

创建内存池ngx_pool_t *  ngx_create_pool(size_t size, ngx_log_t *log);
销毁内存池void ngx_destroy_pool(ngx_pool_t *pool);
重置内存池void ngx_reset_pool(ngx_pool_t *pool);
内存申请(对齐)void *  ngx_palloc(ngx_pool_t *pool, size_t size);
内存申请(不对齐)void *  ngx_pnalloc(ngx_pool_t *pool, size_t size);
内存清除ngx_int_t  ngx_pfree(ngx_pool_t *pool, void *p);

4.1预读知识

内存对齐:

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

封装函数

ngx_alloc:(只是对malloc进行了简单的封装)

void *ngx_alloc(size_t size, ngx_log_t *log)
   2: {
   3:     void  *p;
   4:  
   5:     p = malloc(size);
   6:     if (p == NULL) {
   7:         ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
   8:                       "malloc(%uz) failed", size);
   9:     }
  10:  
  11:     ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
  12:  
  13:     return p;
  14: }

注一:

log是记录时间

ngx_calloc:(调用malloc并初始化为0)

  1: void *ngx_calloc(size_t size, ngx_log_t *log)
   2: {
   3:     void  *p;
   4:  
   5:     p = ngx_alloc(size, log);
   6:  
   7:     if (p) {
   8:         ngx_memzero(p, size);
   9:     }
  10:  
  11:     return p;
  12: }

ngx_memzero:

 #define ngx_memzero(buf, n)       (void) memset(buf, 0, n)

ngx_free :

 1: #define ngx_free          free

 ngx_memalign

#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)

 这里alignment主要是针对部分unix平台需要动态的对齐,对POSIX 1003.1d提供的posix_memalign( )进行封装,在大多数情况下,编译器和C库透明地帮你处理对齐问题。nginx中通过宏NGX_HAVE_POSIX_MEMALIGN来控制;

4.2内存池的创建

遵循RALL

ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    if (p == NULL) {
        return NULL;
    }

    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    p->d.end = (u_char *) p + size;
    p->d.next = NULL;
    p->d.failed = 0;

    size = size - sizeof(ngx_pool_t);
    //#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)
   //内存池最大不超过4095,x86中页的大小为4K
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}

1.nginx对内存的管理分为大内存与小内存,当某一个申请的内存大于某一个值时,就需要从大内存中分配空间否则从小内存中分配空间。
2.nginx中的内存池是在创建的时候就设定好了大小,在以后分配小块内存的时候,如果内存不够,则是重新创建一块内存串到内存池中,而不是将原有的内存池进行扩张。当要分配大块内存是,则是在内存池外面再分配空间进行管理的,称为大块内存池。

4.2内存申请

ngx_palloc

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}

ngx_palloc_small

static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;

    p = pool->current;

    do {
        m = p->d.last;
        //对内存地址进行对齐处理

        if (align) {
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }
         //如果在当前内存块有效范围内,进行内存指针的移动

        if ((size_t) (p->d.end - m) >= size) {
            p->d.last = m + size;

            return m;
        }

        p = p->d.next;如果当前内存块有效容量不够分配,则移动到下一个内存块进行分配

    } while (p);

    return ngx_palloc_block(pool, size);
}

ngx_align_ptr,这是一个用来内存地址取整的宏,非常精巧,一句话就搞定了。作用不言而喻,取整可以降低CPU读取内存的次数,提高性能。因为这里并没有真正意义调用malloc等函数申请内存,而是移动指针标记而已,所以内存对齐的活,C编译器帮不了你了,得自己动手

1: #define ngx_align_ptr(p, a)                                                   \
   2:     (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

2.ngx_palloc_block(ngx_pool_t *pool, size_t size)

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    psize = (size_t) (pool->d.end - (u_char *) pool);
//计算内存池第一个内存块的大小

    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
//分配和第一个内存块同样大小的内存块
    if (m == NULL) {
        return NULL;
    }

    new = (ngx_pool_t *) m;

    new->d.end = m + psize;//设置新内存块的end
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);//将指针m移动到d后面的一个位置,作为起始位置
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;//设置新内存块的last,即申请使用size大小的内存

 //这里的循环用来找最后一个链表节点,这里failed用来控制循环的长度,如果分配失败次数达到5次,
 //就忽略,不需要每次都从头找起    
for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    p->d.next = new;

    return m;
}

3.ngx_palloc_large(ngx_pool_t *pool, size_t size)

ngx_palloc中首先会判断申请的内存大小是否超过内存块的最大限值,如果超过,则直接调用ngx_palloc_large,进入大内存块的分配流程;

1: static void *
   2: ngx_palloc_large(ngx_pool_t *pool, size_t size)
   3: {
   4:     void              *p;
   5:     ngx_uint_t         n;
   6:     ngx_pool_large_t  *large;
   7:     // 直接在系统堆中分配一块空间  
   8:     p = ngx_alloc(size, pool->log);
   9:     if (p == NULL) {
  10:         return NULL;
  11:     }
  12:  
  13:     n = 0;
  14:     // 查找到一个空的large区,如果有,则将刚才分配的空间交由它管理  
  15:     for (large = pool->large; large; large = large->next) {
  16:         if (large->alloc == NULL) {
  17:             large->alloc = p;
  18:             return p;
  19:         }
  20:  
  21:         if (n++ > 3) {
  22:             break;
  23:         }
  24:     }
  25:     //为了提高效率, 如果在三次内没有找到空的large结构体,则创建一个
  26:     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
  27:     if (large == NULL) {
  28:         ngx_free(p);
  29:         return NULL;
  30:     }
  31:  
  32:     large->alloc = p;
  33:     large->next = pool->large;
  34:     pool->large = large;
  35:  
  36:     return p;
  37: }

 4.4内存池重置

void
   2: ngx_reset_pool(ngx_pool_t *pool)
   3: {
   4:     ngx_pool_t        *p;
   5:     ngx_pool_large_t  *l;
   6:     //释放所有大块内存
   7:     for (l = pool->large; l; l = l->next) {
   8:         if (l->alloc) {
   9:             ngx_free(l->alloc);
  10:         }
  11:     }
  12:  
  13:     pool->large = NULL;
  14:     // 重置所有小块内存区  
  15:     for (p = pool; p; p = p->d.next) {
  16:         p->d.last = (u_char *) p + sizeof(ngx_pool_t);
  17:     }
  18: }

 2.5、内存池清理

ngx_pfree

 ngx_pfree(ngx_pool_t *pool, void *p)
   3: {
   4:     ngx_pool_large_t  *l;
   5:     //只检查是否是大内存块,如果是大内存块则释放
   6:     for (l = pool->large; l; l = l->next) {
   7:         if (p == l->alloc) {
   8:             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
   9:                            "free: %p", l->alloc);
  10:             ngx_free(l->alloc);
  11:             l->alloc = NULL;
  12:  
  13:             return NGX_OK;
  14:         }
  15:     }
  16:  
  17:     return NGX_DECLINED;
  18: }

所以说Nginx内存池中大内存块和小内存块的分配与释放是不一样的。我们在使用内存池时,可以使用ngx_palloc进行分配,使用ngx_pfree释放。而对于大内存,这样做是没有问题的,而对于小内存就不一样了,分配的小内存,不会进行释放。因为大内存块的分配只对前3个内存块进行检查,否则就直接分配内存,所以大内存块的释放必须及时。

ngx_pool_cleanup_s

Nginx内存池支持通过回调函数,对外部资源的清理。ngx_pool_cleanup_t是回调函数结构体,它在内存池中以链表形式保存,在内存池进行销毁时,循环调用这些回调函数对数据进行清理。

1: struct ngx_pool_cleanup_s {
   2:     ngx_pool_cleanup_pt   handler;
   3:     void                 *data;
   4:     ngx_pool_cleanup_t   *next;
   5: };

 

handler:是回调函数指针;

data:回调时,将此数据传入回调函数;

next:指向下一个回调函数结构体;

如果我们需要添加自己的回调函数,则需要调用ngx_pool_cleanup_add来得到一个ngx_pool_cleanup_t,然后设置handler为我们的清理函数,并设置data为我们要清理的数据。这样在ngx_destroy_pool中会循环调用handler清理数据;

 4.6 内存池销毁

ngx_destroy_pool

ngx_destroy_pool这个函数用于销毁一个内存池:

void
   2: ngx_destroy_pool(ngx_pool_t *pool)
   3: {
   4:     ngx_pool_t          *p, *n;
   5:     ngx_pool_large_t    *l;
   6:     ngx_pool_cleanup_t  *c;
   7:  
   8:     //首先调用所有的数据清理函数
   9:     for (c = pool->cleanup; c; c = c->next) {
  10:         if (c->handler) {
  11:             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
  12:                            "run cleanup: %p", c);
  13:             c->handler(c->data);
  14:         }
  15:     }
  16:  
  17:     //释放所有的大块内存
  18:     for (l = pool->large; l; l = l->next) {
  19:  
  20:         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
  21:  
  22:         if (l->alloc) {
  23:             ngx_free(l->alloc);
  24:         }
  25:     }
  26:  
  27:     //最后释放所有内存池中的内存块
  28:     for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
  29:         ngx_free(p);
  30:  
  31:         if (n == NULL) {
  32:             break;
  33:         }
  34:     }

 5.总结

5.1内存池创建

NGINX通过ngx_create_pool创建内存池。参数size用来指定从内存池的小块内存区域总共可以获取的内存的最大,同时也是可以申请的单个小块内存的最大size。在函数执行过程中size会转化成16的整数倍。

5.2内存块申请

NGINX提供了几个从内存池中申请内存的API。他们大体流程是一样的。如果申请的内存的size不大于max,如就从小内存区域试着去切分,并且移动内存池的last指针指向新申请内存地址的末端。反之,就通过系统的malloc申请一片内存并且连接到pool的large链表中。

void *ngx_palloc(ngx_pool_t*pool,size_t size)把size大小对齐然后再申请内存,若size大于max,则改用向系统申请。

void *ngx_pnalloc(ngx_pool_t*pool,size_t size)和上面函数唯一的区别是size不用对齐.

void*ngx_pmemalign(ngx_pool_t *pool,size_tsize)无论size大小实际内存都向系统申请,并且加入到内存池的large链表中。其中对应的管理结构ngx_large_t是从内存池中申请。

void *ngx_pcalloc(ngx_pool_t*pool,size_t size)申请并初始化为零。

5.3内存块释放

从内存池中申请的小块内存不能单独释放,只能在内存池释放时被整体被释放。大内存块因为是通过系统调用ngx_alloc申请的,所以,这些内存块可以调用ngx_pfree被单独释放。这两个函数本质上是对系统调用malloc和free的封装。

5.4内存池释放

通过ngx_destroy_pool可以释放创建的内存池。函数首先会调用cleanup链表中的所有函数,然后通过调用free释放large链表中所有通过系统申请的大块内存。最后释放所有的内存池结构这其中就包括所有的小块内存。

5.5优缺点

通过使用内存池,NGINX有效地降低了内存分片,减少了内存泄露的可能。在使用小内存时只是进行了简单粗暴地分割来分配内存。这一方面简化了操作提高了效率。但是,另一方面这些大小不一小块内存因为没有管理信息的维护而不能及时释放和重用。它们只能在整个内存池释放时才能作为一个整体能得以释放。不过因为NGINX本身运行具有的阶段化的特征,特定内存池都只在特定阶段存在,使得内存不能及时释放的影响不是很大。

或许NGINX的内存池也能结合kernel的slab内存池的某些特性。这些slab内存池的内存块也是从一个大的内存区域切分出来,它们被有效地管理起来,可以很方便地进行释放和重用。而且在内存池释放时可以方便地释放掉所有的内存,也可以有效地杜绝内存泄露的发生。但是有些额外的管理开销所以会浪费一些内存,而且每一个内存池只能支持一个size的内存块的申请。需要把这些不同size的内存池有效地组织和管理起来。

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

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

相关文章

[Spring Cloud] nacos作为服务中心

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

【C语言从0到1之指针】(详解,赶紧收藏期末考试备用)

&#x1f57a;作者&#xff1a;启明星使 &#x1f383;专栏&#xff1a;《数据库》《C语言》《数据结构》 &#x1f3c7;分享喜欢的一句话&#xff1a;去发光&#xff0c;而不是等待被照亮​ 目录 1. 指针是什么 内存 指针变量 总结&#xff1a; 外&#xff1a; 2. 指针和…

比 O(nlog(n)) 做得更好 — 5.结束语和基准

这就是 groupSort 真正优于 mergeSort 的地方。 长按关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 扫码关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 最后的想法 我们知道&#xff0c;将一个大问题分解为一系列更小…

vmware虚拟机黑屏问题

&#x1f490;文章适合于所有的相关人士进行学习&#x1f490; 1.问题描述 VMware虚拟机出现了黑屏现象&#xff0c;打开虚拟机挂起能看到显示&#xff0c;但一开就黑屏。下面就给大家说说虚拟机黑屏怎么办&#xff0c;vmware虚拟机开机黑屏的解决方法。 大多数同学在安装第…

策略验证_卖出口诀_长箭射天股价落地

写在前面&#xff1a; 1. 本文中提到的“股票策略校验工具”的具体使用操作请查看该博文&#xff1b; 2. 文中知识内容来自书籍《同花顺炒股软件从入门到精通》 3. 本系列文章是用来学习技法&#xff0c;文中所得内容都仅仅只是作为演示功能使用 目录 解说 策略代码 结果 解…

mysql运行报错:

Install/Remove of the Service Denied!解决办法&#xff1a;Install/Remove of the Service Denied解决办法 上面报错看这篇&#xff1a;MySQL本地连接报错&#xff1a;ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061) 上面报错看这篇&#xf…

【Ubuntu】实现windows和ubuntu之间的共享文件

实现windows和ubuntu之间的共享文件一、配置windows和ubuntu之间的共享文件夹&#xff08;方式1&#xff09;二、通过将windows中的文件拖拽到ubuntu的终端之上&#xff08;方式2&#xff09;一、配置windows和ubuntu之间的共享文件夹&#xff08;方式1&#xff09; VMware菜单…

UE Select File / Folder Window 插件说明

本插件可以在蓝图中打开系统自带的文件/文件夹选择窗口。并且可以在打包出发行包。 1. Open Load File Window 打开文件读取选择窗口&#xff0c;只能单选文件&#xff0c;此函数只是返回文件路径&#xff0c;不会真正读取文件信息 输入 Dialog Title &#xff1a;打开窗口的…

[附源码]java毕业设计图书管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Java EE|软件视角下的操作系统

文章目录前言操作系统的概念及常见的OS举例操作系统的定位以及它的功能/职责/作用全程高能&#xff01;&#xff01;&#xff01;敲黑板警告&#xff01;&#xff01;&#xff01;一、进程概念&#xff08;感性认知&#xff09;二、进程的描述和组织&#xff01;&#xff01;&a…

步进电机实验

一、实验目的&#xff1a; 掌握步进电机的控制方法 二、实验内容与要求&#xff1a; 编写实验程序&#xff0c;利用8255的B口来控制步进电机的运转。 三、实验环境&#xff1a; PC机一台&#xff0c;TD-PITE实验装置一套。 四、实验步骤&#xff1a; 1、参考下图连接实验…

Linux下如何操作寄存器

本期主题&#xff1a; linux下操作寄存器 往期链接&#xff1a; linux设备驱动中的并发linux设备驱动中的编译乱序和执行乱序linux设备驱动之内核模块linux字符驱动linux字符驱动之ioctl部分linux字符驱动之read、write部分linux驱动调试之Debugfs 文章目录1.为什么有这个问题…

内网信息收集(基于红日靶场1)

net view # 查看局域网内其他主机名 net config Workstation # 查看计算机名、全名、用户名、系统版本、工作站、域、登录域 net user # 查看本机用户列表 net user /domain # 查看域用户 net localgroup administrators # 查看本地…

合宙esp32 环境搭建和使用方法

文章目录1.环境搭建2.问题2-1&#xff1a;exec: "cmd": executable file not found in %PATH%3.合宙esp32 使用3-1引脚定义3-1-1&#xff1a;板载LED3-2下载程序&#xff1a;3-3测试程序4.ESP32-C3开发板相关资料1.环境搭建 1-1&#xff1a;下载esp32 安装包 链接&a…

[一篇读懂]C语言八讲:数据结构概述

[一篇读懂]C语言八讲&#xff1a;数据结构概述1. 与408关联解析及本节内容介绍1 与408关联解析2 本节内容介绍2. 逻辑结构与存储结构1 逻辑结构2 存储结构顺序存储链式存储3 顺序存储与链式存储分析顺序存储优缺点链式存储优缺点3. 时间复杂度与空间复杂度1 算法定义2 时间复杂…

攻防世界1-misc

1-misc 题目描述&#xff1a;无 题目环境&#xff1a;https://download.csdn.net/download/m0_59188912/87094807 打开压缩包&#xff0c;提示密码是出题人生日。 使用archpr爆破压缩包。 得到密码&#xff1a;20001228 解压压缩包&#xff0c;得到两个文件&#xff0c;一个图片…

[附源码]java毕业设计网吧购物系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【CSDN|每日一练】代写匿名信

目录 运行结果题目描述输入描述:输出描述:示例代码结语运行结果 题目描述 小Q想要匿名举报XX领导不务正业! 小Q害怕别人认出他的字迹。 他选择从报纸上剪裁下来英文字母组成自己的举报信。 现在小Q找来了报纸,和自己的举报信的Txt, 你能帮他确定一下是否能够完成匿名信…

6242. 二叉搜索树最近节点查询

目录题目关键词代码题目 给你一个 二叉搜索树 的根节点 root &#xff0c;和一个由正整数组成、长度为 n 的数组 queries 。 请你找出一个长度为 n 的 二维 答案数组 answer &#xff0c;其中 answer[i] [mini, maxi] &#xff1a; mini 是树中小于等于 queries[i] 的 最大值…

Vue-Router学习记录

目录 一.使用路由 1.1配置路由 1.2采用路由 二.路由懒加载 三.路由重定向 四.嵌套路由 五.路由跳转 1.1标签式 1.2编程式 1.3路由的query参数 1.4命名路由 前言: vue 属于单页面应用&#xff0c;所谓的路由&#xff0c;就是根据浏览器路径不同&#xff0c;用不同的…