glibc-2.23学习笔记(一)—— malloc部分源码分析

  • 搭建Glibc源码调试环境
    • 1.下载并解压glibc源码
    • 2.配置gdb
    • 3.编译测试程序
  • 第一次调用
  • 源码分析
    • __libc_malloc
    • _int_malloc
      • 函数声明
      • 局部变量
      • start
      • fast bin部分
      • small bin部分
      • large bin部分
      • binmap部分
      • top chunk部分
  • 参考资料

搭建Glibc源码调试环境

1.下载并解压glibc源码

sudo apt-get install glibc-source
cd /usr/src/glibc
sudo tar xvf glibc-2.23.tar.xz

2.配置gdb

打开gdb配置文件

sudo vim ~/.gdbinit

在首行加入以下内容

directory /usr/src/glibc/glibc-2.23/malloc:/usr/src/glibc/glibc-2.23/elf

3.编译测试程序

//test.c
#include <stdio.h>
#include <stdlib.h>int main()
{malloc(0x10);  //第一次调用malloc(0x10);    //重点分析此处return 0;
}
//gcc test.c -o test

第一次调用

第一次调用malloc时会从__malloc_hook中取出malloc_hook_ini函数指针并执行

static void *
malloc_hook_ini (size_t sz, const void *caller)
{__malloc_hook = NULL;         //将__malloc_hook置0ptmalloc_init ();             //初始化ptmallocreturn __libc_malloc (sz);     //回到__libc_malloc
}

源码分析

__libc_malloc

void *
__libc_malloc (size_t bytes)
{mstate ar_ptr;void *victim;/* 判断__malloc_hook中是否有值,有值就当成函数指针调用 */void *(*hook) (size_t, const void *)= atomic_forced_read (__malloc_hook);if (__builtin_expect (hook != NULL, 0))return (*hook)(bytes, RETURN_ADDRESS (0));/* 获取分配区指针,并锁住分配区内存 */arena_get (ar_ptr, bytes);/* 分配内存 */victim = _int_malloc (ar_ptr, bytes);/* 内存分配失败,尝试寻找其他可用的arena进行分配 */if (!victim && ar_ptr != NULL){LIBC_PROBE (memory_malloc_retry, 1, bytes);ar_ptr = arena_get_retry (ar_ptr, bytes);victim = _int_malloc (ar_ptr, bytes);}//解除分配区内存锁if (ar_ptr != NULL)(void) mutex_unlock (&ar_ptr->mutex);/* 通过倒数第二个比特位判断内存属性 */assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||ar_ptr == arena_for_chunk (mem2chunk (victim)));return victim;
}

_int_malloc

函数声明

static void *
_int_malloc (mstate av, size_t bytes){

局部变量

 /* 对齐后的所需内存大小 */INTERNAL_SIZE_T nb;               /* normalized request size *//* 保存所需chunk在bins中的下标 */unsigned int idx;                 /* associated bin index *//* 保存bin */mbinptr bin;                      /* associated bin *//* 保存候选chunk */mchunkptr victim;                 /* inspected/selected chunk *//* 保存chunk的size */INTERNAL_SIZE_T size;             /* its size *//* 保存候选chunk在bins中的下标 */int victim_index;                 /* its bin index *//* 保存从候选chunk分配内存后剩余内存的指针 */mchunkptr remainder;              /* remainder from a split *//* 保存剩余部分内存大小 */unsigned long remainder_size;     /* its size */unsigned int block;               /* bit map traverser */unsigned int bit;                 /* bit map traverser */unsigned int map;                 /* current word of binmap */mchunkptr fwd;                    /* misc temp for linking */mchunkptr bck;                    /* misc temp for linking */const char *errstr = NULL;

start

 /*Convert request size to internal form by adding SIZE_SZ bytesoverhead plus possibly more to obtain necessary alignment and/orto obtain a size of at least MINSIZE, the smallest allocatablesize. Also, checked_request2size traps (returning 0) request sizesthat are so large that they wrap around zero when padded andaligned.*//* 取得对齐后的size值 */checked_request2size(bytes, nb);/* There are no usable arenas.  Fall back to sysmalloc to get a chunk frommmap.  *//* 没有可用的arena,随机分配一块内存并返回 */if (__glibc_unlikely(av == NULL)){void* p = sysmalloc(nb, av);if (p != NULL)alloc_perturb(p, bytes);return p;}

fast bin部分

 /*If the size qualifies as a fastbin, first check corresponding bin.This code is safe to execute even if av is not yet initialized, so wecan try it without checking, which saves some time on this fast path.*//* 如果所需大小小于等于fast bins中的最大size,则尝试从fast bins中分配第一次调用malloc时,max_fast为0 */if ((unsigned long)(nb) <= (unsigned long)(get_max_fast())){idx = fastbin_index (nb);                     //计算所需大小在fast bins中的下标mfastbinptr *fb = &fastbin (av, idx);         //尝试从对应下标中取出堆块指针mchunkptr pp = *fb;/* 若存在可用的bin,将bin从链表中取出,并取出当前bin的fd放入链表尾,fd的值不能和当前bin相同 */do{victim = pp;if (victim == NULL)break;}while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))!= victim);if (victim != 0)      // 若候选chunk存在{/* 检查size位是否属于fast bins */if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)){errstr = "malloc(): memory corruption (fast)";errout:malloc_printerr (check_action, errstr, chunk2mem (victim), av);return NULL;}check_remalloced_chunk (av, victim, nb);      //各种检测,包括堆块大小,是否对齐等等void *p = chunk2mem (victim);                 //返回chunk地址(不包含head)alloc_perturb (p, bytes);return p;       //返回应用层}}

small bin部分

 /*If a small request, check regular bin.  Since these "smallbins"hold one size each, no searching within bins is necessary.(For a large request, we need to wait until unsorted chunks areprocessed to find best fit. But for small ones, fits are exactanyway, so we can check now, which is faster.)*//* 若所需大小属于small bins,则尝试在small bins中分配 */if (in_smallbin_range (nb)){idx = smallbin_index (nb);    //获取所需大小对应下标bin = bin_at (av, idx);       //从arena获取下标对应的bin/* 如果victim等于表头,表示该链表为空 */if ((victim = last (bin)) != bin){/* 如果候选chunk为0表示还没有创建双向循环链表 */if (victim == 0)  /* initialization check */malloc_consolidate (av);    /* 第一次malloc时会调用这个函数合并所有的fast bin */else{/* 否则尝试将victim从small bin中取出 */bck = victim->bk;if (__glibc_unlikely (bck->fd != victim)){errstr = "malloc(): smallbin double linked list corrupted";goto errout;}set_inuse_bit_at_offset (victim, nb);     //设置候选chunk的inuse标志//该标志位于下一个chunk size位的第0个bit/* 将bin从链表中取出,相当于unlink */bin->bk = bck;bck->fd = bin;if (av != &main_arena)victim->size |= NON_MAIN_ARENA;check_malloced_chunk (av, victim, nb);    //各种检测void *p = chunk2mem (victim);             //获得用户部分可用的指针alloc_perturb (p, bytes);return p;     //返回}}}

large bin部分

 /*If this is a large request, consolidate fastbins before continuing.While it might look excessive to kill all fastbins beforeeven seeing if there is space available, this avoidsfragmentation problems normally associated with fastbins.Also, in practice, programs tend to have runs of either small orlarge requests, but less often mixtures, so consolidation is notinvoked all that often in most programs. And the programs thatit is called frequently in otherwise tend to fragment.*//* 若所需大小不属于small bins,则可能位于large bins中 */else  {idx = largebin_index (nb);        //计算所需大小对应large bins的下标if (have_fastchunks (av))         //判断是否存在属于fast bins的空闲chunkmalloc_consolidate (av);        //合并所有的fast bin}/*Process recently freed or remaindered chunks, taking one only ifit is exact fit, or, if this a small request, the chunk is remainder fromthe most recent non-exact fit.  Place other traversed chunks inbins.  Note that this step is the only place in any routine wherechunks are placed in bins.The outer loop here is needed because we might not realize untilnear the end of malloc that we should have consolidated, so mustdo so and retry. This happens at most once, and only when we wouldotherwise need to expand memory to service a "small" request.*/for (;; ){int iters = 0;/* 反向遍历unsorted bins双向循环链表,直到候选chunk指向头节点 */while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)){bck = victim->bk;//判断chunk大小是否合法if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)|| __builtin_expect (victim->size > av->system_mem, 0))/* 如果不合法就执行malloc_printerr打印错误信息 */malloc_printerr (check_action, "malloc(): memory corruption", chunk2mem (victim), av);size = chunksize (victim);     //若合法则取出size位/*If a small request, try to use last remainder if it is theonly chunk in unsorted bin.  This helps promote locality forruns of consecutive small requests. This is the onlyexception to best-fit, and applies only when there isno exact fit for a small chunk.*//* 如果这个chunk大小属于small bins且unsorted bins中只有一个chunk,且这个chunk为last remainder chunk,且这个chunk的大小大于所需的size+MINSIZE */if (in_smallbin_range (nb) &&bck == unsorted_chunks (av) &&victim == av->last_remainder &&(unsigned long) (size) > (unsigned long) (nb + MINSIZE)){/* split and reattach remainder *//* 从这个remainder中取出所需的部分,与表头形成双向循环链表 */remainder_size = size - nb;       //计算取出所需部分后的剩余部分remainder = chunk_at_offset (victim, nb);    //获得chunk指针unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;  //arena指向remainderav->last_remainder = remainder;       //设置新的remainderremainder->bk = remainder->fd = unsorted_chunks (av);             //remainder指向arena/* 如果剩余部分大小不属于small bins,则只能时largebins因此需要将fd_nextsize和bk_nextsize清空,unsorted bin无需这两个成员 */if (!in_smallbin_range (remainder_size)){remainder->fd_nextsize = NULL;remainder->bk_nextsize = NULL;}/* 设置chunk的相关信息 */set_head (victim, nb | PREV_INUSE |(av != &main_arena ? NON_MAIN_ARENA : 0));set_head (remainder, remainder_size | PREV_INUSE);set_foot (remainder, remainder_size);check_malloced_chunk (av, victim, nb);void *p = chunk2mem (victim);     //取得用户部分可用的内存指针alloc_perturb (p, bytes);return p; //返回应用层}/* remove from unsorted list *//* 将bin从unsortedbin中取出 */unsorted_chunks (av)->bk = bck;bck->fd = unsorted_chunks (av);/* Take now instead of binning if exact fit *//* 若size位等于所需大小,则设置标志位,然后将bin取出并返回用户指针 */if (size == nb){set_inuse_bit_at_offset (victim, size);if (av != &main_arena)victim->size |= NON_MAIN_ARENA;check_malloced_chunk (av, victim, nb);void *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;     //返回应用层}/* place chunk in bin *//* 若size属于small bins,则将chunk加入到bck和fwd之间,作为small bins的第一个chunk */if (in_smallbin_range (size)){victim_index = smallbin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;}/* 若size属于large bins,则将chunk加入到bck和fwd之间,作为large bin的第一个chunk */else{victim_index = largebin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;/* maintain large bins in sorted order */if (fwd != bck)   //若fwd不等于bck,说明large bins中存在空闲chunk{/* Or with inuse bit to speed comparisons */size |= PREV_INUSE;/* if smaller than smallest, bypass loop below */assert ((bck->bk->size & NON_MAIN_ARENA) == 0);/* 如果当前size比最后一个chunk size还要小,则将当前size的chunk加入到chunk size链表尾然后将所有大小的链表取出首个chunk链到一起,方便查找 */if ((unsigned long) (size) < (unsigned long) (bck->bk->size)){fwd = bck;bck = bck->bk;victim->fd_nextsize = fwd->fd;victim->bk_nextsize = fwd->fd->bk_nextsize;fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;}else{assert ((fwd->size & NON_MAIN_ARENA) == 0);/* 正向遍历chunk size链表,找到第一个chunk大小小于等于当前大小的chunk */while ((unsigned long) size < fwd->size){fwd = fwd->fd_nextsize;assert ((fwd->size & NON_MAIN_ARENA) == 0);}/* 若已经存在相同大小的chunk,则将当前chunk插入到同大小chunk链表的尾部 */if ((unsigned long) size == (unsigned long) fwd->size)/* Always insert in the second position.  */fwd = fwd->fd;/* 否则延伸出一个大小等于当前size的chunk链表,将该链表加入到chunk size链表尾 */else{victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;}bck = fwd->bk;}}else  //large bins中没有 chunk,直接将当前 chunk 加入 chunk size链表victim->fd_nextsize = victim->bk_nextsize = victim;}/* 将当前chunk加入large bins的空闲链表中 */mark_bin (av, victim_index);victim->bk = bck;victim->fd = fwd;fwd->bk = victim;bck->fd = victim;/* 最多遍历10000个unsorted bin,节约时间 */#define MAX_ITERS       10000if (++iters >= MAX_ITERS)break;}/*If a large request, scan through the chunks of current bin insorted order to find smallest that fits.  Use the skip list for this.*//* 当处理完unsorted bins后,使用最佳匹配法匹配chunk */if (!in_smallbin_range (nb))      //判断chunk是否位于large bins中{bin = bin_at (av, idx);/* skip scan if empty or largest chunk is too small *//* 判断large bins是否为空,以及链表中的最大size是否满足所需大小 */if ((victim = first (bin)) != bin && (unsigned long) (victim->size) >= (unsigned long) (nb)){/* 遍历chunk size链表,找到大于等于所需大小的chunk链表 */victim = victim->bk_nextsize;while (((unsigned long) (size = chunksize (victim)) < (unsigned long) (nb)))victim = victim->bk_nextsize;/* Avoid removing the first entry for a size so that the skiplist does not have to be rerouted.  *//* 为了尽量不破坏链表结构,尝试取出victim->fd作为候选chunk */if (victim != last (bin) && victim->size == victim->fd->size)victim = victim->fd;/* 计算剩余size,然后断链 */remainder_size = size - nb;unlink (av, victim, bck, fwd);/* Exhaust *//* 若剩余部分小于MIN_SIZE,则将整个chunk分配给应用层(可以搞事情嗷) */if (remainder_size < MINSIZE){set_inuse_bit_at_offset (victim, size);if (av != &main_arena)victim->size |= NON_MAIN_ARENA;}/* Split */else{/* 获得剩余部分chunk指针 */remainder = chunk_at_offset (victim, nb);/* We cannot assume the unsorted list is empty and thereforehave to perform a complete insert here.  *//* 剩余部分作为新chunk加入到unsorted bins中 */bck = unsorted_chunks (av);fwd = bck->fd;if (__glibc_unlikely (fwd->bk != bck)){errstr = "malloc(): corrupted unsorted chunks";goto errout;}remainder->bk = bck;remainder->fd = fwd;bck->fd = remainder;fwd->bk = remainder;/* 若剩余部分大小属于large bin,则将fd_nextsize和bk_nextsize清零因为这两个指针对于unsorted bin无用 */if (!in_smallbin_range (remainder_size)){remainder->fd_nextsize = NULL;remainder->bk_nextsize = NULL;}/* 设置各种标志位 */set_head (victim, nb | PREV_INUSE |(av != &main_arena ? NON_MAIN_ARENA : 0));set_head (remainder, remainder_size | PREV_INUSE);set_foot (remainder, remainder_size);}check_malloced_chunk (av, victim, nb);void *p = chunk2mem (victim);     //获取用户部分指针  alloc_perturb (p, bytes);return p;     //返回应用层}}

binmap部分

     /*Search for a chunk by scanning bins, starting with next largestbin. This search is strictly by best-fit; i.e., the smallest(with ties going to approximately the least recently used) chunkthat fits is selected.The bitmap avoids needing to check that most blocks are nonempty.The particular case of skipping all bins during warm-up phaseswhen no chunks have been returned yet is faster than it might look.*//* 在small bins和large bins中都没有找到大小合适的chunk尝试从大小比所需大小更大的空闲chunk中寻找合适的 *//*  获取下一个相邻bin的空闲chunk链表,并获取该bin对于binmap中的bit位的值binmap中标识了相应bin中是否存在空闲chunk,按照block进行管理每个block为一个int,共32bit,可以表示32个bin中是否存在空闲chunk使用binmap主要时为了加快查找空闲chunk的效率这里只查询比所需chunk大的bin中是否有空闲chunk可用 */++idx;bin = bin_at (av, idx);block = idx2block (idx);map = av->binmap[block];bit = idx2bit (idx);for (;; ){/* Skip rest of block if there are no more set bits in this block.  *//* 若bit > map,说明map为0,则该block对应的所有bins都没有空闲chunk  */if (bit > map || bit == 0){/* 遍历下一个block,直到找到一个不为0的block或遍历完所有的block */do{if (++block >= BINMAPSIZE) /* out of bins *//* 没有找到合适chunk,尝试使用top chunk分配 */goto use_top;}while ((map = av->binmap[block]) == 0);/* 设置bin指向block的第一个bit对应的bin */bin = bin_at (av, (block << BINMAPSHIFT));bit = 1;  //将bit置为1,表示该block中bit1对应的bin}/* Advance to bin with set bit. There must be one. *//* 在block中遍历对应的bin,直到找到一个不为0的bit */while ((bit & map) == 0){bin = next_bin (bin);bit <<= 1;assert (bit != 0);}/* Inspect the bin. It is likely to be non-empty *//* 将chunk加入链表尾 */victim = last (bin);/*  If a false alarm (empty bin), clear the bit. *//* 若victim与bin链表头指针相同,表示该bin中没有空闲chunkbinmap中的相应位设置不准确,将其清零 */if (victim == bin){av->binmap[block] = map &= ~bit; /* Write through */bin = next_bin (bin);bit <<= 1;}else{size = chunksize (victim);    //获得size/*  We know the first chunk in this bin is big enough to use. *//* 判断chunk大小是否满足 */assert ((unsigned long) (size) >= (unsigned long) (nb));remainder_size = size - nb;       //计算分配后的剩余大小/* unlink */unlink (av, victim, bck, fwd);    //将chunk断链/* Exhaust *//* 若剩余大小小于MINSIZE,则将整个chunk分配给用户 */if (remainder_size < MINSIZE){set_inuse_bit_at_offset (victim, size);if (av != &main_arena)victim->size |= NON_MAIN_ARENA;}/* Split */else{/* 获得chunk指针 */remainder = chunk_at_offset (victim, nb);/* We cannot assume the unsorted list is empty and thereforehave to perform a complete insert here.  *//* 剩余部分作为新chunk加入到unsorted bins中 */bck = unsorted_chunks (av);fwd = bck->fd;if (__glibc_unlikely (fwd->bk != bck)){errstr = "malloc(): corrupted unsorted chunks 2";goto errout;}remainder->bk = bck;remainder->fd = fwd;bck->fd = remainder;fwd->bk = remainder;/* advertise as last remainder *//* 若分配大小属于small bin,将last_remainder设置为剩余部分构成的chunk */if (in_smallbin_range (nb))av->last_remainder = remainder;/* 若剩余部分大小属于large bin,则将fd_nextsize和bk_nextsize清零因为这两个指针对于unsorted bin无用 */if (!in_smallbin_range (remainder_size)){remainder->fd_nextsize = NULL;remainder->bk_nextsize = NULL;}/* 设置各种标志位 */set_head (victim, nb | PREV_INUSE |(av != &main_arena ? NON_MAIN_ARENA : 0));set_head (remainder, remainder_size | PREV_INUSE);set_foot (remainder, remainder_size);}check_malloced_chunk (av, victim, nb);    //各种检测void *p = chunk2mem (victim);     //获得用户部分指针alloc_perturb (p, bytes);return p;     //返回应用层}}

top chunk部分

 use_top:/*If large enough, split off the chunk bordering the end of memory(held in av->top). Note that this is in accord with the best-fitsearch rule.  In effect, av->top is treated as larger (and thusless well fitting) than any other available chunk since it canbe extended to be as large as necessary (up to systemlimitations).We require that av->top always exists (i.e., has size >=MINSIZE) after initialization, so if it would otherwise beexhausted by current request, it is replenished. (The mainreason for ensuring it exists is that we may need MINSIZE spaceto put in fenceposts in sysmalloc.)*//* 获得top chunk指针与大小 */victim = av->top;size = chunksize (victim);/* 必须满足top chunk size > nb + MINSIZE的情况下才能分配 */if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)){/* 从top chunk分配内存后,剩余的部分将作为新的top chunk */remainder_size = size - nb;remainder = chunk_at_offset (victim, nb);av->top = remainder;/* 设置各种标志位 */set_head (victim, nb | PREV_INUSE |(av != &main_arena ? NON_MAIN_ARENA : 0));set_head (remainder, remainder_size | PREV_INUSE);check_malloced_chunk (av, victim, nb);void *p = chunk2mem (victim);     //获得用户部分指针alloc_perturb (p, bytes);return p;     //返回应用层}/* When we are using atomic ops to free fast chunks we can gethere for all block sizes.  *//* 若top chunk也无法满足要求,则检查fast bins中是否存在空闲chunk若存在,则将所有的fast bins合并,然后尝试从small bins和large bins获取下标 */else if (have_fastchunks (av)){malloc_consolidate (av);/* restore original bin index */if (in_smallbin_range (nb))idx = smallbin_index (nb);elseidx = largebin_index (nb);}/*Otherwise, relay to handle system-dependent cases*/else{/* 所有方法都行不通,最后的解决方案是向系统申请一块新的内存 */void *p = sysmalloc (nb, av);if (p != NULL)alloc_perturb (p, bytes);return p;}}
}

参考资料

《Glibc内存管理Ptmalloc源代码分析》

glibc-2.23学习笔记(一)—— malloc部分源码分析相关推荐

  1. Nginx学习笔记(五) 源码分析内存模块内存对齐

    Nginx源码分析&内存模块 今天总结了下C语言的内存分配问题,那么就看看Nginx的内存分配相关模型的具体实现.还有内存对齐的内容~~不懂的可以看看~~ src/os/unix/Ngx_al ...

  2. Netty学习笔记(一)Netty客户端源码分析

    最近在学些BIO,NIO相关的知识,也学习了下Netty和它的源码,做个记录,方便以后继续学习,如果有错误的地方欢迎指正 如果不了解BIO,NIO这些基础知识,可以看下我的如下博客 IO中的阻塞.非阻 ...

  3. 【SLAM学习笔记】6-ORB_SLAM3关键源码分析④ Optimizer(一)单帧优化

    2021SC@SDUSC 目录 1.前言 2.代码分析 1.前言 Optimizer是非常重要的代码文件!! 这一部分代码量巨大,查阅了很多资料结合来看的代码,将分为以下部分进行分析 1. 单帧优化 ...

  4. 【SLAM学习笔记】12-ORB_SLAM3关键源码分析⑩ Optimizer(七)地图融合优化

    2021SC@SDUSC 目录 1.前言 2.代码分析 1.前言 这一部分代码量巨大,查阅了很多资料结合来看的代码,将分为以下部分进行分析 单帧优化 局部地图优化 全局优化 尺度与重力优化 sim3优 ...

  5. 【SLAM学习笔记】11-ORB_SLAM3关键源码分析⑨ Optimizer(六)地图回环优化

    2021SC@SDUSC 目录 1.前言 2.代码分析 1.前言 这一部分代码量巨大,查阅了很多资料结合来看的代码,将分为以下部分进行分析 单帧优化 局部地图优化 全局优化 尺度与重力优化 sim3优 ...

  6. Android学习笔记-常用的一些源码,防止忘记了

    Android学习笔记-常用的一些源码,防止忘记了... 设置拨打电话 StringdialUri="tell:"+m_currentTelNumble; IntentcallIn ...

  7. Ceph 学习——OSD读写流程与源码分析(一)

    消息从客户端发送而来,之前几节介绍了 客户端下 对象存储.块存储库的实现以及他们在客户端下API请求的发送过程(Ceph学习--Librados与Osdc实现源码解析 . Ceph学习--客户端读写操 ...

  8. Java的wait()、notify()学习三部曲之一:JVM源码分析

    原文链接:https://blog.csdn.net/boling_cavalry/article/details/77793224 综述 Java的wait().notify()学习三部曲由三篇文章 ...

  9. Redis学习之intset整数集合源码分析

    1.整数集合:整数的集合,升序排序,无重复元素 2.整数集合intset是集合键的底层实现之一,当一个集合只包含整数值的元素,并且这个集合的元素数量不多时,redis会使用整数集合作为集合键的底层实现 ...

最新文章

  1. PHP简单封装MysqlHelper类
  2. Flutter 动画全解析(动画四要素、动画组件、隐式动画组件原理等)
  3. javascript基础拾遗——词法作用域
  4. HDU5863 cjj's string game(DP + 矩阵快速幂)
  5. c++ 弧形面如何逆时针排序_环形导轨如何实现拐弯?
  6. matlab求系统根轨迹代码_根轨迹法、PID参数整定和matlab指令计算
  7. java 很垃圾_JAVA吧真的很垃圾!!!
  8. Android调试相关的技术常识
  9. 用Python实现一个简单的智能换脸软件
  10. Identityserver4中ResourceOwnerPassword 模式获取refreshtoken
  11. CSS只是进化的一部分
  12. Unity3D 游戏引擎之实现平面多点触摸(二)
  13. git21天打卡day11-删除分支
  14. 多个Excel文件合并成一个文件
  15. NetSetMan v3.4.1
  16. VTN系列多通道振弦模拟信号采集仪常规操作
  17. 列联表分析——独立性检验(卡方检验)
  18. gba口袋妖怪c语言源代码,查看“精灵宝可梦 火红·叶绿”的源代码
  19. 大淘客的index.php,index.php · zenozhengs/大淘客CMS底部菜单修改版 - Gitee.com
  20. 网易mumu模拟器adb连接配置

热门文章

  1. CV之PoseEstimation:Pose Estimation人体姿态估计(AI识人,OpenPose+DeepCut+RMPE+Mask RCNN)的简介、案例应用之详细攻略
  2. 成功解决RuntimeWarning: invalid value encountered in double_scalars
  3. 数据科学-通过数据探索了解我们的特征
  4. 亲爱的,热爱的~CTF
  5. TCP协议三步挥手与四步挥手
  6. 日期时间类,按特定格式显示日期时间
  7. Netty源码 服务端的启动
  8. 【hdu 1527】取石子游戏
  9. mybatis 动态 SQL
  10. Asp.net Core 使用Redis存储Session