STL空间配置器、vector、list、deque、map复习

时间:2023-03-08 16:50:10
STL空间配置器、vector、list、deque、map复习

本文写于2017-03-03,从老账号迁移到本账号,原文地址:https://www.cnblogs.com/huangweiyang/p/6440830.html

STL的六大组件:容器、算法、迭代器、空间配置器、容器适配器、仿函数。

空间配置器

空间配置器产生的缘由:由于程序需求,很多小块内存在程序中动态申请、释放。于是就容易出现内存外部碎片问题,同时由于一直调用malloc系统调用,产生性能问题。
(注:内碎片:因为内存对齐/访问效率而差生如用户需要3字节,实际得到4字节的问题,其中的碎片是浪费掉的。外碎片:系统内存总量足够,但是不连续,所以无法分配给用户使用而产生的浪费)。

实现策略:
用户申请空间大于128字节?大于就调用一级空间配置器,使用malloc分配内存。否则调用二级空间配置器,从内存池分配内存。一级空间配置器使用malloc/free,还增加了类似new_handler的malloc_handler机制。默认通过USE_MALLOC宏选择配置。

二级空间配置器也就是内存池,维护一个*链表数组,类似于哈希桶,每个桶负责分配8,16,24,直到128,以8的倍数为内存分配单元,每个桶连接一个*链表,将对应大小的内存块链接起来。分配内存的基本过程,用户申请内存,检查*链表是挂有空闲节点,如果有直接取走。如果没有,*链表向内存池申请节点。内存池如果有空间,直接去内存链接到链表,返回一个给用户。如果没有,需要使用malloc扩充内存池,扩充的大小是需求的两倍加上一个随配置次数增加愈来愈大的附加量。如果malloc也失败了,系统heap没有足够空间了,那么内存池就会四处寻找没有分配的且足够大的区块,找到了就分配给用户。找不到就主动调用第一级空间配置器,因为第一级空间配置器有out-of-memory处理机制,或许可以释放其他内存拿来此处使用如果可以就成功,否则发出bad_alloc异常。

vector

vector的数据安排与操作方式,与array非常相似。两者唯一区别在于空间的灵活运用性。array是静态空间,一旦配置就不能改变。vector是动态空间,可随时扩充空间容纳新元素。当然为了提高vector的效率,我们可以使用reserve()函数提前分配空间,这样push_back就不用频繁分配空间了。vector当旧空间满载时,如果要增加新的元素,不是在原有位置上持续更新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原来大小的两倍另外配置一块较大的空间,然后将原空间内容拷贝过来,再构造新元素,并释放原空间。因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器都失效了。当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效。vector内存空间由空间配置器分配,即使vector进行clear操作,这步分内存也不会释放掉。如果要释放掉vector的内存,可以使用临时变量+swap的方法。vector随机存储效率高,但插入元素效率低,因为要移动很多的数目,vector因此都不提供push_front()方法。

list

vector和数组类似,它拥有一段连续的内存空间,并且起始地址不变,很好的支持了随机存取,但由于是连续空间,所以在中间进行插入、删除等操作时都造成了内存块的拷贝和移动,另外在内存空间不足时还需要重新申请一大块内存的拷贝。为了克服这些缺陷,STL定义了另一种容器list,它对于数据插入和删除的时间复杂度均为O(1),,而且内存方面不用频繁的拷贝转移。

List是一个双向链表,所以迭代器是一个双端迭代器。在vector中如果进行插入和删除操作后迭代器会失效,list有一个重要的性质就是插入和接合操作都不会造成原有的迭代器失效。删除时仅有被删除节点迭代器失效。

STL在list最末尾节点和头结点之间插入一个头指针,它的prev指向尾节点,next指向头结点,仅凭一个指针就可以表现出整个list。

deque

vector支持随机访问,list支持高效删除,而deque支持随机访问以及首尾元素的高效删除。deque容器采用的是一种整体不连续,但是分段连续的存储空间,使用一个map数组即中控器来管理这些空间。deque的迭代器有四个属性组成,curr、first、last、node。curr指向真实元素,符合STL左开右闭。first和last是每个缓冲区已分配元素的左右边界。node指向中控器,用来实现连续空间已满时迭代器跳跃到另一片连续空间。deque容器有两个迭代器成员,start和finish,分别指向当前所用的第一个有效缓冲区和最后一个缓冲区。这就为deque的首尾增删操作提供了O(1)时间的复杂度。同时对于随机访问,通过调用start迭代器重载的[]实现,如果随机访问的元素在同一缓冲区,curr指针直接加上偏移量就可以访问。如果不在,通过连续缓冲区大小计算要跳跃到的缓冲区,然后跳到相应的偏移量即可访问。

使得deque容器失效的操作,在deque首部或尾部插入操作时,通常迭代器不会失效。插入端如果没有空间了,并不会直接分配空间。而是先判断另外一端是否有足够的空间(map_size>2*已使用的map个数),如果有,则只需调整map相应指针即可。如果没有,则进行申请空间、复制元素、删除旧空间的三部曲操作,这时迭代器才会失效。在deque中间做插入或删除时,STL会判断一下两边元素的数目,移动元素数目少的一边为插入元素挪出地方,所以元素少的一边迭代器会失效!

map

map是关联容器,以键值对的形式进行存储,方便进行查找。关键词起到索引的作用,表示与索引相关的数据。以红黑树的结构实现,插入删除等操作都在O(lgn)时间内完成。

set

set也是关联容器,set中每个元素只包含一个关键字。set支持高效的关键字查询操作--检查一个给定的关键字是否在set中。set也是红黑树实现,支持高效插入删除。