c++ 标准库的各种容器(vector,deque,map,set,unordered_map,unordered_set,list)的性能考虑

转自:http://blog.csdn.net/truexf/article/details/17303263

一、vector

vector采用一段连续的内存来存储其元素,向vector添加元素的时候,如果容量不足,vector便会重新malloc一段更大的内存,然后把原内存中的数据memcpy到新的内存中,并free原内存块,然后将新元素加入。vector的元素插入性能跟以下几个要素关系重大:

1. 插入的位置

头部插入:将所有元素后移,然后将新元素插入

中间插入:将插入点后面的元素后移,然后插入新元素

尾部插入:将新元素直接插入尾部

尾部插入无疑是最快的,头部插入最慢,中间插入次之,慢的点在于插入前要移动内存。

删除元素也是同样的道理。

2. 保留空间大小

如果插入元素是,空间不足将导致重新malloc以及一系列的内存拷贝。如果使用者能对容量有预期,那么采用reserve()来预先分配内存,将大大的提高性能。

3. 内存扩展算法的库实现相关

在空间不足导致需要重新malloc的时候,不同的库实现有很大的不同,往往特定平台的实现会结合操作系统的平台特性以及malloc算法,提供相当优秀的内存扩展算法,在百万次vector<int>的插入操作中,不提供reserve()的情况向,性能表现非常优秀。接近于使用了reserve()的情况。

因为vector采用连续的内存存储其元素,因此其支持元素的下标法随机访问,且时间复杂度是常量0;

如果是查找元素,vector的find()成员函数对元素进行查找时是采用从头到尾扫描的方式,他时间复杂度是O(n),如果vector要应付查找的性能需求,那么应该采取排序的vector,利用算法库的getlowerbound()进行元素的有序插入,利用binary_search()对元素进行二分查找。这种情况下其查找性能不输于基于红黑树的set和map,更是令list望尘莫及。

综述,vector适用于尾部插入,但是此时无法兼顾查找的性能,因为二分查找的vector要求重新排序,或者要求vector在插入时就保持有序,这样就无法做到尾部插入。

但是vector作为动态数组的替代,已经足够优秀。

二、deque

deque采用多块内存串起来的方式提供其元素的存错,每一个内存块存储多个元素,每一块内存存储的元素个数相同,这是他不同于vector采用一块内存来存错所有的元素的方式。这样带来的好处是:

首先,头部插入和尾部插入/删除元素的成本是一样,弥补了vector再头部插入元素性能不佳的问题;

其次,对于vector的一个内存块的模式,当有巨大数量的元素,操作系统的大内存分配和赋值时很缓慢的,而且deque的方式就不会带来这个问题。

缺点是:

首先对元素的访问需要经过两个层次,第一次找到元素所在的内存块,第二次找到块中的元素。不过这个时间几乎是可以忽略,除非对性能要求极其苛刻。

其次,对其进行排序,以及排序后的查找会比较慢,想象知名的排序算法,都是针对一段连续内存进行下标访问,而deque是断续的内存块组成。同样排序后的折半查找也无法利用下标直接访问自然性能大打折扣。

三、list

list很简单,他就是个双向链表。每一个节点的内存都是独立的。理论上,其优点是任何位置的插入删除元素操作都是非常块的。缺点是不适合用于元素的查找,因为他只能是扫描的方式。根据实际的测试情况来看,我认为list不值得一用,因为节点的频繁新增与删除将导致大量重复的内存分配和释放操作。而实际上,操作系统以及运行库的内存分配与释放频率和策略才是影响stl各大容器的性能最关键的点。

四、map/multi_map/set/multi_set

这四个数据结构是采用平衡树(红黑树)实现其元素在内存中的存储。理论上(与list一样是理论上)他们的性能是很高,而且在插入/删除与元素查找上是的平衡点掌握的相当好的,具体的算法复杂度可参考红黑树算法文献,但是同样基于“操作系统以及运行库的内存分配与释放频率和策略才是影响stl各大容器的性能最关键的点”这一法则,他们在理论之外,实际的应用中,总是表现得不是太好。

五、散列容器unordered_map/unordered_set/unordered_multi_map/unordered_multi_set

c++11引入的散列容器,散列容器具有不稳定性:他依赖于实际所使用的散列算法。而针对不同的元素数量,不同的散列算法具有相当大的性能差异。

所以,理论上(同样是理论上)他们的算法时间复杂度是近乎“常量”(如果要处理冲突那就不是了)。但时间上对于一般用户使用起来可能会带来风险。除非你对所使用的散列算法和元素数量都有很好的预估。在我一些简单的测试(采用int元素类型,采用std的默认散列函数)中,散列容器的性能是排名垫底的。

六、“内存分配与释放频率和策略才是影响stl各大容器的性能最关键的点”

这点非常重要。不同的操作系统,不同的运行库,他的虚拟内存管理算法,以及运行库的malloc和free的内部实现都是有差异的。如何找出最有效率的使用标准库容器的方式,一定要结合这一点进行大量的测试才能得出。

有一点可以确认的是:各个标准库的实现,已经是在遵循c++标准的基础上,在性能和适用性上可以说做到了极致。因此很多人总想自己造重复的轮子是大可不必。理解好各种容器的实现原理,再结合实际应用的需求,选择合适的容器,以及容器的使用方式,才是上上之策。

时间: 2024-08-13 18:56:27

c++ 标准库的各种容器(vector,deque,map,set,unordered_map,unordered_set,list)的性能考虑的相关文章

STL标准容器类学习笔记之(Vector/Deque/List)

STL标准容器类简介 1.顺序性容器 vector相当与数组,从后面快速的插入与删除,直接访问任何元素 deque双队列,从前面或后面快速的插入与删除,直接访问任何元素 list双链表,从任何地方快速插入与删除 2.关联容器 set快速查找,不允许重复值 multiset快速查找,允许重复值 map一对一映射,基于关键字快速查找,不允许重复值 multimap一对多映射,基于关键字快速查找,允许重复值 3.容器适配器 stack后进先出 queue先进先出 priority_queue最高优先级

C++标准库之顺序容器

通用概念. 顺序容器基本理解:按照顺序储存元素,并提供 元素具体操作(迭代器或其他) 和 顺序访问元素 的能力. 个人理解: 1)顺序:按照添加的先后次序排序.但,先进优先,还是后进优先取决于具体容器. 个人应用: 1)在可以使用容器时,尽量使用容器.因为你无法保证自己的算法优于整个开发组. 2)选择容器的基本原则(from C+ primer): 1,在不知道如何选择时,使用vector. 2,单个元素需求内存很小,内存紧缺时,不要使用list/forward_list. 3,已知添加操作时,

谈谈两种标准库类型---string和vector

两种最重要的标准库---string和vector string和vector是两种最重要的标准库类型,string表示可变长的字符序列,vector存放的是某种给定类型对象的可变长序列. 一.标准库类型string   1.定义和初始化string对象:初始化string对象的方式有 string s1   默认初始化,s1是一个空串   string s2(s1)   s2是s1的副本 string s2=s1   等价于s2(s1),s2是s1的副本 string s3("value&qu

《深入实践C++模板编程》之六——标准库中的容器

1.容器的基本要求 a.并非所有的数据都可以放进容器当中.各种容器模板对所存数据类型都有一个基本要求——可复制构造.将数据放进容器的过程就是通过数据的复制构造函数在容器内创建数据的一个副本的过程. b.容器中必须有若干与所存数据类型有关的嵌套定义类型. C::value_type 容器所存数据类型 C::reference 容器数据的引用类型 C::const_reference 容器数据的只读引用类型 C::size_type 容器容量类型,通常是一个无符号整数类型 c.除嵌套类型定义外,容器

C++ Primer(一)_标准库_顺序容器

目录 顺序容器 顺序容器 选择什么容器根据业务需求, 研读STL剖析了解底层数据结构, 更加清楚各种优势劣势 零碎点 迭代器被设置为左闭右合带来的编程假设 begin == end,范围为空 begin != end, 至少一个元素 begin可递增至end 两大类型的容器初始化--同类型容器拷贝,迭代器范围拷贝 前者要求容器类型一致 后者只要求元素可转换 两大类型的容器赋值--=号赋值,assign赋值 前者用于列表或同类型容器 后者用于迭代器,初始化列表,(n,elem)方式:限制顺序容器

【C++标准库】STL容器

STL容器的共通能力 所有容器提供的都是"value语义"而非"reference语义".容器进行元素的安插动作时,内部进行copy或者move,而不是管理元素的reference. 元素在容器内有其特定顺序. 一般,各项操作并非绝对安全,调用者需要确保传给操作函数的实参符合条件. const vector<int> v1 = { 1,2,3,4,5,6 }; //使用初值列初始化 vector<float> v2(v1.begin(), v

【C++标准库】特殊容器

特殊容器,又称为容器适配器(Container Adapter),它们改造了标准STL容器,使之满足特殊的要求. Stack堆栈 使用stack时,需包含头文件<stack> push() 将一个元素压入栈内 pop() 从栈内移除下一个元素,但是并不返回它 top()         返回栈内下一个元素,但并不移除它. 如果stack内没有元素,top()和pop()会导致不明确的行为,可采用size()或empty()来检查容器是否为空. Queue队列 Queue实现出了一个FIFO先进

C++标准库之关联容器

基本常识: 1)主要为:map 和 set.衍生型:multi和unorder.例如:multimap, unorder_multimap. 2)使用pair作为单位元素. 1,key-value组成一个pair. 2,first成员:key.second成员:value. 3,key为const类型不可改变. 3)关联容器的构成:key-pair. 基本要点: 1)通常不对关联容器使用 泛型算法. 2)有序关联容器,有 严格弱序 的要求.默认使用 [<=].当然,也可以使用谓词或lambda表

C++教程 扩展标准库第一部分

今天给大家带来的是C++扩展标准库的一些知识点的讲解!这是一部分,后面还会更新另外的一部分! ok,今天我们说另一个小工具,这个工具在处理一些复杂数据的时候会有一些意想不到的效果,不过这得和大家说一些,我是一个标准的标准库忠实者,所以我写程序基本都会优先考虑库,然后在世boost库,而接下来的东西我会借助标准库的东西进行扩展的. 既然是标准库的扩展,那么第一步我们可以先将标准库的东西再进行一些简化,当然这些简化只是为了我们在调试程序的时候方便使用一些. C++的标准输出输入流为了简便都提供了一个