您的位置:首页 > 编程语言 > C语言/C++

unordered_map与线性结构内存占比

2016-01-15 09:44 387 查看
转自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
从表中可以看到,内存消耗从小到大依次是:vector >> map >> unordered_map.其中map所使用的内存是vector的6倍,而unordered_map的内存消耗是map的1.1倍。
当然,以上只是存储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的优势变得并不明显。因为随着对象增大,用于存储对象的空间所占的比例越来越大。因此单从内存消耗的角度来看,当存储的是小对象时,vector占很大的优势。但是,当存储的对象本身大小增大时,它的优势变得不再那么明显。
三、查询效率

说到查询效率,在vector无序的情况下,它的查询复杂度是O(n)的,所以当数据量稍大时,查询效率是十分低下的。但如果vector的元素有序的情况又如何呢?我们通过如下代码进行测试,其中三个容器都包含10000个<int, int>键值对元素,我们进行100万次查询,并对比它们的耗时情况:



[cpp] view
plaincopy

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
从上表可以看到,有序的vector采用二分查找时,查询效率与unordered_map的效率是相当的。而map的查询效率是vector的3.4倍。故而尽管理论上map的查询效率也是O(lgn),但是它仍然别有序的vector采用二分查找的效率低许多。
五、关于unordered_map的空间利用率问题的考虑

从以上的内存占用和访问效率的情况看,似乎unordered_map永远都是最佳的选择。但是在下这个结论之前,我们先来看考虑一种情况,我们有类A,这个为我们的真正存数据的类。一个Record类,它用于存储A,其中需要以一个int类型的值为key(类型不会太多,假设最多只有20个),表示不同类型的A类对象;一个Container类,它用一个vector存储类Record的对象。其中Record存储类A的对象可以用vector,也可以用unordered_map。代码如下:

[cpp] view plaincopyclass 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
可以看到unordered_map的内存消耗是vector的近5倍,所以这种情况如果维护Record中的数据有序的成本不高的话,应该使用vector,而非unordered_map。
六、如何选择

当我们需要使用键值对时,我们应该综合考虑内存和效率两方面。以下是本人的一些个人建议:

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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息