关于map、hash_map 和 unordered_map 的简单性能测试

本文通过实验对比了C++标准库中的三种容器(map、hash_map、unordered_map)在插入、查找和删除操作上的性能差异,特别是在不同类型的键值(整型和字符串)下的表现。结果显示,对于整型键值,map在查找操作上表现出色;而对于字符串键值,unordered_map在插入和查找操作上更为高效。此外,文章还分析了不同类型键值对容器性能的影响,并提供了一个关键结论:在选择容器时,应综合考虑键值类型、所需性能和内存需求。
测试环境:
DELL Latitude D610
CPU:Pentium-M 1.73 GHz
内存:2.00GB
OS:Windows 2003
开发环境: Visual Studio 2010
编译参数:/MD /Ox /Ot /W3 /D "_CRT_SECURE_NO_WARNINGS" /D "_CRT_NONSTDC_NO_WARNINGS" /link /MACHINE:X86 kernel32.lib

测试用代码:

#include <hash_map>
#include <unordered_map>
#include <map>
#include <string>
#include <iostream>
#include <ctime>
#include <windows.h>
using namespace std;

const int TEST = 100000;

class test_cls {
public:
	test_cls(){}
	test_cls(const string& s1, const string& s2, const string& s3, const string& s4, int n1, int n2):
			str1(s1), str2(s2), str3(s3), str4(s4), num1(n1), num2(n2){}
private:
	string str1;
	string str2;
	string str3;
	string str4;
	int num1;
	int num2;
};

template<typename T>
void test1(const string& test_str)
{
	T t;
	typedef typename T::value_type the_type;
	clock_t start = clock();
	for(int i = 0; i < TEST; ++i)
		t.insert(the_type(i, test_cls("s1", "s2", "s3", "s4", 1, 2)));
	cout << test_str << " insert:" << clock() - start << endl;
	
	start = clock();
	for(int i = 0; i < TEST; ++i)
		t.find(i);
	
	cout << test_str << " find:" << clock() - start << endl;
	
	start = clock();
	for(int i = 0; i < TEST; ++i)
		t.erase(i);
	
	cout << test_str << " erase:" << clock() - start << endl;
}

int main()
{
	test1<map<int, test_cls> >("map");
	::Sleep(1000);
	test1<hash_map<int, test_cls> >("hash_map");
	::Sleep(1000);
	test1<unordered_map<int, test_cls> >("unordered_map");

	return 0;
}

结果(为了减小误差,我对代码进行了10次测试):

map<int, test_cls>

次数插入查询删除
11251578
21251563
31251563
41251563
51091678
61091678
71251578
81251578
91251563
101251578

hash_map<int, test_cls>

次数插入查询删除
11253147
21253147
31251563
41253147
51253147
61251563
71253147
81093147
91253147
101103147


unordered_map<int, test_cls>

次数插入查询删除
11253147
21401662
31251563
41401662
51253147
61251563
71253147
81093147
91253147
101253147


        从上面的结果来看,map、hash_map 和 unordered_map在插入上性能差不多,但在查询上map的性能更好,而在删除上hash_map 和 unordered_map性能差不多。相比各类书籍和网上对 hash_map 和 unordered_map 与 map 的性能描述来说,似乎大相径庭。

        其实这在情理之中。因为,此处使用的key类型为int,对于数值型来说(包括;char、short、long)它们在map中排序(<) 和 hash_map 和 unordered_map中的散列值计算和比较(==)都是基于基本的内置运算,效率上没什么太大差异,因此,在插入上结果几乎相同,至于在查询上,之所以map较快,也是因为它只是对key在做简单<比较,而 hash_map 和 unordered_map 却要再次计算查询量的散列值而后再做==比较。至于删除,因为map内部是会对数据进行排序的,因此,相比hash_map 和 unordered_map不排序的而言,其在删除时除了要进行查询、移除、修正外还需要对数据结构进行调整


为了验证上述结论,我将key由int改为string:

#include <hash_map>
#include <unordered_map>
#include <map>
#include <string>
#include <iostream>
#include <ctime>
#include <cstdio>
#include <windows.h>
using namespace std;

const int TEST = 100000;

class test_cls {
public:
	test_cls(){}
	test_cls(const string& s1, const string& s2, const string& s3, const string& s4, int n1, int n2):
			str1(s1), str2(s2), str3(s3), str4(s4), num1(n1), num2(n2){}
private:
	string str1;
	string str2;
	string str3;
	string str4;
	int num1;
	int num2;
};

template<typename T>
void test1(const string& test_str)
{
	T t;
	typedef typename T::value_type the_type;
	clock_t start = clock();
	for(int i = 0; i < TEST; ++i)
		t.insert(the_type(i, test_cls("s1", "s2", "s3", "s4", 1, 2)));
	cout << test_str << " insert:" << clock() - start << endl;
	
	start = clock();
	for(int i = 0; i < TEST; ++i)
		t.find(i);
	
	cout << test_str << " find:" << clock() - start << endl;
	
	start = clock();
	for(int i = 0; i < TEST; ++i)
		t.erase(i);
	
	cout << test_str << " erase:" << clock() - start << endl;
}

template<typename T>
void test2(const string& test_str)
{
	T t;
	typedef typename T::value_type the_type;
	string* fill_str = new string[TEST];
	char tmp[10];
	for(int i = 0; i < TEST; ++i)
	{
		sprintf(tmp, "%d", i);
		fill_str[i] = tmp;
	}
	clock_t start = clock();
	for(int i = 0; i < TEST; ++i)
		t.insert(the_type(fill_str[i], test_cls("s1", "s2", "s3", "s4", 1, 2)));
	cout << test_str << " insert:" << clock() - start << endl;
	
	start = clock();
	for(int i = 0; i < TEST; ++i)
		t.find(fill_str[i]);
	
	cout << test_str << " find:" << clock() - start << endl;
	
	start = clock();
	for(int i = 0; i < TEST; ++i)
		t.erase(fill_str[i]);
	
	cout << test_str << " erase:" << clock() - start << endl;
}

int main()
{
	//test1<map<int, test_cls> >("map");
	//::Sleep(1000);
	//test1<hash_map<int, test_cls> >("hash_map");
	//::Sleep(1000);
	//test1<unordered_map<int, test_cls> >("unordered_map");
	
	test2<map<string, test_cls> >("map");
	::Sleep(1000);
	test2<hash_map<string, test_cls> >("hash_map");
	::Sleep(1000);
	test2<unordered_map<string, test_cls> >("unordered_map");

	return 0;
}

结果:

map<string, test_cls>

次数插入查询删除
120462141
220462141
318878140
418878140
520462141
620462141
718878125
820462141
918878141
1018878141

hash_map<string, test_cls>

次数插入查询删除
11563262
21573162
31573162
41413178
51573162
61573162
71573178
81573262
91573162
101573162

unordered_map<string, test_cls>

次数插入查询删除
11413147
21411662
31411563
41571563
51411662
61413147
71573147
81571563
91413147
101411662


        因此,在选择map、hash_map 和 unordered_map 时,除了要根据实际工作需要在速度和内存方面有所抉择以外。最主要的还是要看key的类型,如果key的类型是内置的数值型的话,完全可以考虑使用map,这样就可以鱼和熊掌兼得——既可以得到很好的效率,而且不必多费内存

### C++ `unordered_map` 与 `hash_map` 的区别、用法及性能特点 #### 区别 1. 标准化状态:`unordered_map` 是 C++ 标准库的一部分,自 C++11 起被引入并广泛支持。而 `hash_map` 并非 C++ 标准库的一部分,而是某些编译器(如 GNU C++)提供的扩展容器[^2]。 2. 可移植性:由于 `hash_map` 不属于标准库,其跨平台兼容性可移植性较差。相比之下,`unordered_map` 在所有支持 C++11 的编译器中均可使用[^5]。 #### 用法 - **`unordered_map`**: - 需要包含头文件 `<unordered_map>`。 - 声明方式为 `std::unordered_map<键类型, 值类型>`。 - 示例代码如下: ```cpp #include <iostream> #include <unordered_map> #include <string> int main() { std::unordered_map<std::string, int> myMap = {{"apple", 5}, {"banana", 3}, {"orange", 8}}; for (const auto& pair : myMap) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; } ``` - **`hash_map`**: - 需要包含特定的头文件,例如在 GNU C++ 中为 `<ext/hash_map>`。 - 声明方式为 `__gnu_cxx::hash_map<键类型, 值类型>`。 - 示例代码如下: ```cpp #include <iostream> #include <ext/hash_map> #include <string> using namespace __gnu_cxx; int main() { hash_map<std::string, int> myHashMap = {{"apple", 5}, {"banana", 3}, {"orange", 8}}; for (const auto& pair : myHashMap) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; } ``` #### 性能特点 1. **时间复杂度**:两者均基于哈希表实现,提供平均 O(1) 的插入、查找删除操作复杂度[^1]。 2. **空间利用率**:由于 `unordered_map` 是标准库的一部分,其实现经过优化,在大多数情况下具有更高的空间利用率更好的性能表现。 3. **扩展性**:`unordered_map` 支持更多的自定义选项,例如通过模板参数指定哈希函数等价比较函数,从而更灵活地适配不同的键类型[^4]。 #### 注意事项 对于内置类型(如 `int`、`std::string` 等),`unordered_map` `hash_map` 均无需额外定义哈希函数或等价比较函数。但对于自定义类型,需要重载 `operator==` 定义适当的哈希函数。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值