c++ 中的 map 和 unordered_map 在内存使用方面有啥区别吗?

Posted

技术标签:

【中文标题】c++ 中的 map 和 unordered_map 在内存使用方面有啥区别吗?【英文标题】:Is there any difference between map and unordered_map in c++ in terms of memory usage?c++ 中的 map 和 unordered_map 在内存使用方面有什么区别吗? 【发布时间】:2019-10-19 16:19:32 【问题描述】:

我在 InterviewBit 上解决一个问题并遇到一个问题, 这是链接https://www.interviewbit.com/problems/diffk-ii/。 当我使用 c++ STL map 来解决这个问题时,它会向我显示消息

超出内存限制。您的提交未在分配的内存限制内完成。 这是我的代码

int Solution::diffPossible(const vector<int> &A, int B) 
    int n = A.size();
    map< int , int > mp;
    for(int i =0;i<n; i++)
        mp[A[i]] = i;
    int k = B;
    for(int i =0; i<n; i++)
        if(mp.find(A[i]+k) != mp.end() && mp[A[i]+k] != i)
            return 1;
        
        if(mp.find(A[i]-k) != mp.end() && mp[A[i]-k] != i)
            return 1;
        
    

    return 0;

当我用 unorderd_map 替换 map 时,解决方案被接受。 这是代码

int Solution::diffPossible(const vector<int> &A, int B) 
    int n = A.size();
    unordered_map< int , int > mp;
    for(int i =0;i<n; i++)
        mp[A[i]] = i;
    int k = B;
    for(int i =0; i<n; i++)
        if(mp.find(A[i]+k) != mp.end() && mp[A[i]+k] != i)
            return 1;
        
        if(mp.find(A[i]-k) != mp.end() && mp[A[i]-k] != i)
            return 1;
        
    

    return 0;

这意味着 map 比 unordered_map 占用更多的内存。 谁能解释这是怎么回事?为什么地图占用更多内存 空间大于 unordered_map?

【问题讨论】:

这是一个很难很好回答的问题,所以我要退缩了。本质上,容器的 payload 的大小与系统上的指针相比。这意味着哈希映射将占用相对较少的空间。我喜欢你巧妙地解决这个问题!但请注意,您可以在 O(N) 内解决此问题,而无需重新存储输入数组。 Advantages of Binary Search Trees over Hash Tables的可能重复 我已链接的副本中接受的答案直接回答了这个问题。 对于小内存,很难击败flat_setflat_map(例如在 boost 中可用,显然在标准化过程中),即排序向量。 mapunordered_map 应该有类似的内存要求,我认为这是幸运的,在你的特定实现中刚好低于阈值。 @Bathsheba 你如何达到 O(N)?您有不涉及某种排序算法的解决方案吗? 【参考方案1】:

    地图被实现为二叉搜索树,每个节点(除了有用的数据)通常存储3个指针(指向一个左孩子、右孩子和父母)。

    无序映射被实现为哈希表,其中每个节点都在一个链表中。对于单链表(属于相关存储桶),每个节点只有 1 个指针更新:但是,每个存储桶还有一个额外的指针。在没有冲突的理想情况下,每个存储在内存中的元素会有 2 个指针

请注意,2 个ints 通常会占用 8 个字节,与单个指针相同。


例如,查看 GNU libstdc++ 实现。 RB树的节点定义如下:

struct _Rb_tree_node_base

  typedef _Rb_tree_node_base* _Base_ptr;
  typedef const _Rb_tree_node_base* _Const_Base_ptr;

  _Rb_tree_color    _M_color;
  _Base_ptr     _M_parent;
  _Base_ptr     _M_left;
  _Base_ptr     _M_right;
  ...

在那里,您可以观察到这 3 个指针。


一般来说,很难说哪个容器会消耗更少的总内存。但是,我创建了一个基准,将 1M 随机数插入到两个容器中,并测量了 最大驻留大小 (MaxRSS) 以反映所有消耗的内存空间,包括例如堆管理数据。结果如下:

    48,344 kBstd::map50 888 kB 用于std::unordered_map, 对于std::unordered_mapreserve40,932 kB

请注意,由于桶列表的重新分配,无序地图(广告 2.)的内存消耗更高。这就是reserve 成员函数的用途。如果一个人关心内存消耗并且事先知道元素的数量,他/她应该总是预先分配(这与向量的情况相同)。

【讨论】:

父母在二叉搜索树中是可选的,但是我不确定 std::map 实现是否使用它们。编辑:我不是反对者 这取决于实现,但通常哈希表会保留一个大数组(或多个数组)来存储数据。我不明白哈希表如何使用链表并保留O(1) 查找。 @WeaktoEnumaElish C++ unordered_map 使用一个桶数组,每个桶在一个链表中存储节点(带有键值对)。请参阅,例如,***.com/q/31112852/580083 和 ***.com/q/21518704/580083 进行扩展讨论。 哦,这就是你的意思。虽然每个节点在技术上都在一个链表中,但我会说数组是哈希表的更真实形式。 “单链表”->“单链表”。否则意味着只有链表。【参考方案2】:

映射基本上是二叉搜索树,而 unordered_map 实现为哈希映射。如果您查看两者的实现,您会很快注意到 BST 要大得多。

这也意味着 map 比 unordered_map 慢很多。

                | map             | unordered_map
---------------------------------------------------------
Ordering        | increasing  order   | no ordering
                | (by default)        |

Implementation  | Self balancing BST  | Hash Table
                | like Red-Black Tree |  

search time     | log(n)              | O(1) -> Average 
                |                     | O(n) -> Worst Case

Insertion time  | log(n) + Rebalance  | Same as search

Deletion time   | log(n) + Rebalance  | Same as search

英国夏令时:

struct node

    int data;
    node* left;
    node* right;
;

哈希映射:

struct hash_node 
    int key;
    int value;
    hash_node* next;

参考:https://www.geeksforgeeks.org/map-vs-unordered_map-c/

【讨论】:

您应该知道,在插入无序地图时,最坏的情况经常发生。标准设置是每个项目使用一个桶 + 一些桶作为储备。因此,插入比保留的存储桶更多的项目意味着调整存储桶数组的大小并重新散列。 顺便说一句,HashMap 通常是一个数组而不是列表。使用列表时,您将失去全部性能优势。 这取决于实现,但是哈希映射与二叉树的典型缺点是哈希映射需要保留大块内存来哈希项目。二叉树的节点更大,但它只需要与项目一样多的节点。 @user6556709 C++ 标准要求 unordered_map 使用 open hashing,这是一个存储桶数组,其中数据存储在存储桶中(通常为每个存储桶使用链表)桶)。 @user6556709 不,他们不是。它们在一个链表中。你可以在这里查看:wandbox.org/permlink/JSFJB4PM5VjvUtbp。这 2 个元素距离太远,无法放入数组中(否则距离为 8)。

以上是关于c++ 中的 map 和 unordered_map 在内存使用方面有啥区别吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 标准委员会是不是打算在 C++11 中 unordered_map 破坏它插入的内容?

leetcode 982

C++ 中的 map 与 hash_map

unordered_map 迭代器指向 end(),如何从 unordered_map 中检索键?

c++ 中的 map 和 unordered_map 在内存使用方面有啥区别吗?

LeetCode 229 求众数 II[Map] HERODING的LeetCode之路