readme
c++标准库中map容器的增删改查操作十分常见,这篇博客对map容器的“删”动作做了较为详细的测试,“删”有不止一种方法,效率是有差异的。
测试程序
//common.h
#ifndef _COMMON_H_
#define _COMMON_H_
#include <string>
#include <stdio.h>
#include <set>
#include <memory>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <utility>
#include <algorithm>
#include <map>
#include <numeric>
#include <vector>
#include <iostream>
#include <iterator>
#include <sys/time.h>
using namespace std;
#endif
//Timer.h
#ifndef _TIMER_H
#define _TIMER_H
#include <sys/time.h>
#include "common.h"
class Timer
{
public:
Timer(string strCallInfo = "Timer")
{
_desc = strCallInfo;
gettimeofday(&_begin,NULL);
gettimeofday(&_end, NULL);
};
~Timer()
{
gettimeofday(&_end,NULL);
printf("%s, cost %ldms\n", _desc.c_str(), (_end.tv_sec - _begin.tv_sec)*1000 + (_end.tv_usec - _begin.tv_usec)/1000);
};
private:
Timer(){};
string _desc;
timeval _begin;
timeval _end;
};
#endif
//map_erase.cpp
#include "common.h"
#include "Timer.h"
void asseble_map(map<string, int> &stringIntMap){
stringIntMap.clear();
for (int i = 0; i < 1000000; ++i){
char key_char[64];
sprintf(key_char, "%d_key", i);
string keyString(key_char);
stringIntMap[keyString] = i;
}
}
int main(void){
map<string, int> stringIntMap;
vector<string> stringVector;
int i = 0;
asseble_map(stringIntMap);
for (i = 0; i < 1000000; ++i){
char key_char[16];
sprintf(key_char, "%d_key", i + 500000);
string key_string(key_char);
stringVector.push_back(key_string);
}
{
Timer timer("erase_iter_after_find");
for (i = 0; i < 1000000; ++i){
std::map<string, int>::iterator pos = stringIntMap.find(stringVector[i]);
if (pos != stringIntMap.end())
{
stringIntMap.erase(pos);
}
}
}
asseble_map(stringIntMap);
{
Timer timer("erase_key_after_find");
for (i = 0; i < 1000000; ++i){
std::map<string, int>::iterator pos = stringIntMap.find(stringVector[i]);
if (pos != stringIntMap.end())
{
stringIntMap.erase(stringVector[i]);
}
}
}
asseble_map(stringIntMap);
{
Timer timer("erase_key_directly");
for (i = 0; i < 1000000; ++i){
stringIntMap.erase(stringVector[i]);
}
}
return 0;
}
编译
g++ -o map_erase map_erase.cpp -O0 -g -Wall -std=c++11
执行
[root@***]# ./map_erase
erase_iter_after_find, cost 628ms
erase_key_after_find, cost 1056ms
erase_key_directly, cost 847ms
总结
c++中map结构是十分常用的数据结构,其erase函数的官方定义中有三种重载,分别为:
iterator erase (const_iterator position);
size_type erase (const key_type& k);
iterator erase (const_iterator first, const_iterator last);
其中第三种重载用于删除两个迭代器之间的所有元素,在此不做讨论。
前两种都用于删除某一对kv,用法很相似,区别在于传入的参数是迭代器还是k本身。
第一种方法(erase_iter_after_find)需要先找到目标k在map结构中的迭代器,如果目标k不在map中,则找不到对应的迭代器,不可进行erase操作。如果目标k存在于map中,erase返回一个迭代器,指向被删除迭代器后一个迭代器。
第二种方法(erase_key_after_find)与第一种方法类似,区别在于传入的参数是k本身。
第三种方法(erase_key_directly)更为简单随意一些,直接传入一个k即可,不管目标k在map中存在与否都可以正确执行,erase返回被删除元素的数量。
经过测试,第一种方法效率最高。究其原因,可以分为两部分,下面进行详细解析。
第一部分,删除map中的元素必须要先找到该元素在map中的位置,这三种方法都进行了查找,其中第一种方法一开始就进行了查找,并且仅仅查找了一次;第二种方法一开始也进行了查找,但是传入的参数是k本身,erase的时候还会再一次进行一次内部隐式查找,一共查找了两次;第三种方法在erase内部隐式的进行查找,查找了一次。
第二部分,如上文所属,第一种方法和第三种方法都仅仅查找了一次,第一种方法更高效的原因是什么呢?erase操作最终的目标是删除迭代器,第三种方法需要有一步生成一个迭代器,构造迭代器的消耗产生了这两种方法的效率差异。