C++ STL Deque源码阅读纪要

一、deque是什么

    deque是双向队列,队头和队尾都能进行插入和删除,这是与vector不同之处之一。另外与vector的一点不同之处是deque的内存由分段连续空间组成,没有容量的概念,即没有vector在需要扩容时进行的开辟新数组、复制旧数据、清空旧数组的三部曲之说。其内存结构如下图。

    如图,deque的内存由两部分构成,第一部分是一个由指针构成的map数组,第二部分是多个分段的缓冲区,每个缓冲区可看成是一个内存连续的数组。map数组的中间部分是已使用部分,两端是未使用部分,随着数据的不断增加,两端的空闲节点会用于指向新的缓冲区。这里,map隐式的存在一,装填因子的概念,如上图的装填因子是0.375,这个在后面的内存管理上会用到

二、deque内存管理

    deque的内容很多,比如它的迭代器设计,源码写的很精彩,诸如数据复制过程中的内存覆盖问题都考虑到了,有兴趣的同学可以下载并参考: 侯捷 STL源码剖析。这里重点谈谈内存管理,比如初始化的内存是什么样子,什么时候map数组需要扩容,什么操作会触发map的扩容等。

    2.1、 缓冲区大小

        STL允许指定缓冲区大小,如果不指定,则使用默认大小512个字节。缓冲区的节点数量的计算方式为:如果元素大小小于512个字节,则缓冲区内的元素数量为 512 / se,否则为1,其中 se 表示元素大小,以字节为单位。

    2.2、 map的初始化

        缓冲区容量 = 512 / se > 1 ? 512 / se  :  1

        map的需要节点数 = (元素个数 / 缓冲区容量) + 1, 这里富余出一个节点

        map的初始化大小为max(8,  map的需要节点数 + 2)。初始化的map及其关联的缓冲区如下图。

    其中,hit 和 tit 分别表示指向头部缓冲区的map节点和尾部缓冲区的map节点,两端空余的节点数是基本相同的,这是为了后续使得两侧的扩容能力相同

    2.3、 map的扩容

       必要条件是map前端已无可用节点或者尾端仅有 1 个可用节点,这一般是由头部和尾部的操作不均衡导致。

        如果装填因子接近50%,会重新分配一块空间是原有map数组两倍左右大小的连续空间并将原有的数据拷贝到新的数组中,释放原map,并修改迭代器的指向。

        如果装填因子不足50%,则map仅做内部的数据copy移动,如下图所示。

        即将已用节点全部移动到map的中间部位,使两端有大致相同的扩容能力。假定两端的操作频率是相同的

三、deque优缺点

     deque本质上是一个二维数组,通过map进行索引。它的优点是无需像vector那样进行三部曲,实质上从上述分析可以看出,deque的扩容是细粒度的、缓慢的,而vector是粗粒度的、快速的。

     从短期来看,deque要比vector节省空间,它总是每次分配一小块空间供程序使用;但从长期看,随着插入和删除这些操作的进行,deque的缓冲区可能会稀疏化,造成空间浪费。

发布了94 篇原创文章 · 获赞 31 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/gaoxueyi551/article/details/103660709