A recent talk about unordered_map in C++ made me realize that I should use unordered_map for most cases where I used map before, because of the efficiency of lookup ( amortized O(1) vs. O(log n) ). Most times I use a map, I use either int or std::string as the key type; hence, I've got no problems with the definition of the hash function. The more I thought about it, the more I came to realize that I can't find any reason of using a std::map over a std::unordered_map in the case of keys with simple types -- I took a look at the interfaces, and didn't find any significant differences that would impact my code.
因此,问题是:是否有任何真正的理由使用std::map而不是std::unordered_map在简单类型的情况下,如int和std::string?
我是从严格的编程角度提出这个问题的——我知道它并不是完全标准的,而且它可能会给移植带来问题。
另外,我认为正确答案之一可能是“对于较小的数据集更有效”,因为开销更小(是真的吗?)——因此,我希望将这个问题限制在键数量非普通的情况下(>1 024)。
编辑:哦,我忘记了显而易见的(感谢GMan!)——是的,地图当然是有序的——我知道,我正在寻找其他原因。
不要忘记map保持其元素的顺序。如果你不能放弃它,显然你不能使用unordered_map。
另外需要记住的一点是,unordered_map通常会使用更多的内存。Map只有一些内部指针和每个对象的内存。相反,unordered_map有一个大数组(在某些实现中会变得相当大),然后为每个对象提供额外的内存。如果需要内存感知,map应该会更好,因为它缺少大数组。
所以,如果你需要纯粹的查找-检索,我认为unordered_map是最好的方法。但总会有权衡,如果你负担不起,那你就不能使用它。
仅凭个人经验,我发现在主实体查找表中使用unordered_map而不是map时,性能有了巨大的改进(当然是度量的)。
另一方面,我发现它在重复插入和删除元素时要慢得多。它非常适合相对静态的元素集合,但如果您正在进行大量的插入和删除,那么哈希+桶似乎就会累加起来。(注意,这需要经过多次迭代。)
I was intrigued by the answer from @Jerry Coffin, which suggested that the ordered map would exhibit performance increases on long strings, after some experimentation (which can be downloaded from pastebin), I've found that this seems to hold true only for collections of random strings, when the map is initialised with a sorted dictionary (which contain words with considerable amounts of prefix-overlap), this rule breaks down, presumably because of the increased tree depth necessary to retrieve value. The results are shown below, the 1st number column is insert time, 2nd is fetch time.
g++ -g -O3 --std=c++0x -c -o stdtests.o stdtests.cpp
g++ -o stdtests stdtests.o
gmurphy@interloper:HashTests$ ./stdtests
# 1st number column is insert time, 2nd is fetch time
** Integer Keys **
unordered: 137 15
ordered: 168 81
** Random String Keys **
unordered: 55 50
ordered: 33 31
** Real Words Keys **
unordered: 278 76
ordered: 516 298
总结
假设顺序不重要:
如果你打算一次构建一个大的表,并做很多查询,使用std::unordered_map
如果你要构建一个小的表(可能少于100个元素)并进行大量的查询,请使用std::map。这是因为它的读数是O(log n)
如果你要改变表很多,那么std::map可能是一个不错的选择。
如果你有疑问,请使用std::unordered_map。
历史背景
在大多数语言中,无序映射(又名基于哈希的字典)是默认映射,但在c++中,你得到的是有序映射作为默认映射。这是怎么发生的?有些人错误地认为c++委员会用他们独特的智慧做出了这个决定,但不幸的是,事实比这更丑陋。
It is widely believed that C++ ended up with ordered map as default because there are not too many parameters on how they can be implemented. On the other hand, hash based implementations has tons of things to talk about. So to avoid gridlocks in standardization they just got along with ordered map. Around 2005, many languages already had good implementations of hash based implementation and so it was more easier for the committee to accept new std::unordered_map. In a perfect world, std::map would have been unordered and we would have std::ordered_map as separate type.
性能
下面两张图不言自明:
我大致同意GMan的观点:根据使用类型的不同,std::map可以(而且通常)比std::tr1::unordered_map快(使用VS 2008 SP1中包含的实现)。
有几个复杂的因素需要记住。例如,在std::map中,您正在比较键,这意味着您只查看足够多的键的开头,以区分树的左右子分支。根据我的经验,几乎只有当你使用int这样可以在单个指令中进行比较的时候,你才会查看整个键。对于更典型的键类型,如std::string,通常只比较几个字符。
相比之下,一个像样的哈希函数总是查看整个键。IOW,即使查找表的复杂度是恒定的,哈希本身也具有大致的线性复杂度(尽管是键的长度,而不是项的数量)。使用长字符串作为键,std::map可能会在unordered_map开始搜索之前完成搜索。
其次,虽然有几种方法可以调整哈希表的大小,但大多数方法都非常慢——除非查找比插入和删除频繁得多,否则std::map通常会比std::unordered_map快。
当然,就像我在对你上一个问题的评论中提到的,你也可以使用树表。这既有优点也有缺点。一方面,它将最坏的情况限制在一棵树上。它还允许快速插入和删除,因为(至少当我这样做时)我使用了固定大小的表。消除所有的表大小调整可以让你的哈希表更简单,通常更快。
另一点:哈希和基于树的映射的需求是不同的。哈希显然需要一个哈希函数和一个相等比较,其中有序映射需要一个小于比较。当然,我提到的混合型需要两者兼备。当然,对于使用字符串作为键的常见情况,这并不是真正的问题,但某些类型的键比哈希更适合排序(反之亦然)。