转自http://blog.csdn.net/acaiwlj/article/details/49781877
一、引言
当我们需要使用键值对的情况时,通常我们会使用map或者unordered_map。其中map底层是采用红黑树实现的,它的查询复杂度是O(lgn);unordered_map实际上是hash_map的实现,理论上它的查询复杂度是O(1)的。那么当我们需要使用键值对结构时,我们是否就一定需要使用它们,或者说它们就一定是最好的选择呢?
答案是否定的,实际上我们可以使用元素为std::pair的vector对象来实现键值对。那么这三者之间究竟有什么区别呢?我们应该如何在这三者之间进行抉择呢?接下来我们从内存和访问效率两个方面分别来看这三者的区别。
二、内存使用
由于我们无法直接获取stl容器所使用的内存大小,所以我们这里采用从任务管理器中查看进程的方式来比较三种方式内存消耗情况(当然,可以采用第三方库来查看程序内存消耗情况,此处我们只是做近似的估计,所以直接采用任务管理器查看进程内存消耗情况)。我们比较存储1000万个<int, int>键值对时,三者内存消耗的大小:
方式 | 消耗内存大小 |
vector<pair<int,int>
>
|
8,204KB
|
map<int,
int>
|
47,336KB
|
unordered_map<int,
int>
|
47,748KB
|
当然,以上只是存储int这种小对象,我们继续看看当存储的对象较大时,三者所占的情况。下表给出的结果结果是存储100万个<int, Test>键值对,三者所占内存的情况,其中Test对象包含一个大小为20个double数组:
方式 | 消耗内存大小 |
vector<pair<int,Test>
>
|
321,326KB
|
map<int,
Test>
|
360,452KB
|
unordered_map<int,
Test>
|
360,928KB
|
三、查询效率
说到查询效率,在vector无序的情况下,它的查询复杂度是O(n)的,所以当数据量稍大时,查询效率是十分低下的。但如果vector的元素有序的情况又如何呢?我们通过如下代码进行测试,其中三个容器都包含10000个<int, int>键值对元素,我们进行100万次查询,并对比它们的耗时情况:
- void testMap(map<int, int> & mapT)
- {
- for (int i = 0; i < 1000000; ++i)
- {
- int key = rand() % 10000;
- mapT[key] += 1;
- }
- }
- void testHashMap(unordered_map<int, int> & mapT)
- {
- for (int i = 0; i < 1000000; ++i)
- {
- int key = rand() % 10000;
- mapT[key] += 1;
- }
- }
- void binaryFind(vector<pair<int, int>> & vecT)
- {
- bool n = false;
- static auto comparerer = [](int key, pair<int, int>& keyVal)
- {
- return key == keyVal.first;
- };
- for (int i = 0; i < 1000000; ++i)
- {
- int key = rand() % 10000;
- auto iter = std::upper_bound(vecT.begin(), vecT.end(), key, comparerer);
- iter->second += 1;
- }
- }
- int main()
- {
- {
- map<int, int> mapT;
- for (int i = 0; i < 10000; i++)
- {
- mapT[i] = i;
- }
- auto nowClock = clock();
- testMap(mapT);
- cout << "map cost " << (clock() - nowClock) << "ms" << endl;
- }
- {
- unordered_map<int, int> mapT;
- for (int i = 0; i < 10000; i++)
- {
- mapT[i] = i;
- }
- auto nowClock = clock();
- testHashMap(mapT);
- cout << "unordered_map cost " << (clock() - nowClock) << "ms" << endl;
- }
- {
- vector<pair<int, int>> vecT;
- for (int i = 0; i < 10000; i++)
- vecT.push_back(pair<int, int>(i, i));
- auto nowClock = clock();
- binaryFind(vecT);
- cout << "vector cost " << (clock() - nowClock) << "ms" << endl;
- }
- getchar();
- return 0;
- }
以上代码的运行结果如下表所示:
方式 | 运行时间 |
vector<pair<int,int>
>
|
46ms
|
map<int,
int>
|
157ms
|
unordered_map<int,
int>
|
43ms
|
五、关于unordered_map的空间利用率问题的考虑
从以上的内存占用和访问效率的情况看,似乎unordered_map永远都是最佳的选择。但是在下这个结论之前,我们先来看考虑一种情况,我们有类A,这个为我们的真正存数据的类。一个Record类,它用于存储A,其中需要以一个int类型的值为key(类型不会太多,假设最多只有20个),表示不同类型的A类对象;一个Container类,它用一个vector存储类Record的对象。其中Record存储类A的对象可以用vector,也可以用unordered_map。代码如下:
- class A
- {
- public :
- double data[20];
- };
- class Record
- {
- public:
- unordered_map<int, A> record; // 或者 vector<pair<int, A>> record;
- };
- class Container
- {
- public:
- vector<Record> recorList;
- };
对于Record中选择vector或者unordered_map,占用的内存究竟会有多大区别呢?这里我们在Container中插入10万个Record对象进行对比,一下为对比结果:
方式 |
消耗内存大小
|
vector<pair<int,
A>>
|
334,732k
|
unordered_map<int,
A>
|
469,196k
|
从结果上看,使用unordered_map是vector的1.4倍,而且当我们的对象A的大小减小,这一比值会急剧攀升。假如A中只包含一个double,那对比结果如下:
方式 |
消耗内存大小
|
vector<pair<int,
A>>
|
36,528k
|
unordered_map<int,
A>
|
155,824k
|
六、如何选择
当我们需要使用键值对时,我们应该综合考虑内存和效率两方面。以下是本人的一些个人建议:
1.元素为pair的vector
1)当vector不会频繁的在中间或者列表头插入、删除,且列表有序维护的成本低时,可以考虑使用vector,并利用二分查找来查找指定元素;
2)当数据量较大,内存不足以使用unordered_map时,应当使用vector替代;
2.map
1)当数据量不大,并且key的类型无法计算hash值,可以考虑使用map替代vector,以将我们从vector列表有序的维护中解放出来;
2)当数据量不大,且我们需要有序的访问键值对时,可以考虑使用map替代unordered_map;
3.unordered_map
1)在内存允许并且我们不要求有序的访问所有元素的情况下,我们应该尽量使用unordered_map;