C ++ 11 unordered_map时间复杂度

时间:2014-01-22 11:08:46

标签: c++ c++11 hashmap time-complexity unordered-map

我正在试图找出为资源执行缓存的最佳方法。我主要是寻找原生的C / C ++ / C ++ 11解决方案(即我没有提升和喜欢的选项)。

从缓存中检索时我正在做的事情是这样的:

Object *ResourceManager::object_named(const char *name) {
    if (_object_cache.find(name) == _object_cache.end()) {
        _object_cache[name] = new Object();
    }
    return _object_cache[name];
}

其中_object_cache的定义类似于:std::unordered_map <std::string, Object *> _object_cache;

我想知道的是关于这样做的时间复杂性,是否找到了触发线性时间搜索,还是作为某种查找操作完成?

我的意思是如果我在给定的例子上做_object_cache["something"];它将返回该对象,或者如果它不存在,它将调用默认构造函数插入一个不是我想要的对象。我发现这有点违反直觉,我希望它以某种方式报告(例如,返回nullptr),value无法检索到key,而不是猜猜我想要的是什么。

但是,如果我在密钥上执行find,它是否会触发一个大搜索,实际上它将以线性时间运行(因为找不到密钥会查看每个密钥)?< / p>

这是一个很好的方法吗,或者是否有人有一些建议,也许可以使用查找或某些东西来知道密钥是否可用,我可以经常访问,如果是这样的话花一些时间用于搜索我想消除它,或者至少尽可能快地进行搜索。

感谢您的任何意见。

5 个答案:

答案 0 :(得分:4)

默认构造函数(由_object_cache["something"]触发)是你想要的;指针类型的默认构造函数(例如Object *)给出nullptr(8.5p6b1,脚注103)。

所以:

auto &ptr = _object_cache[name];
if (!ptr) ptr = new Object;
return ptr;

使用无序地图(auto &ptr)的引用作为本地变量,以便分配到地图并在同一操作中设置返回值。在C ++ 03中或者如果你想要显式,请写Object *&ptr(对指针的引用)。

请注意,您应该使用unique_ptr而不是原始指针来确保缓存管理所有权。

顺便说一下,findoperator[]的效果相同;平均常数,最坏情况线性(仅当无序映射中的每个键具有相同的散列时)。

答案 1 :(得分:3)

以下是我写这个的方法:

auto it = _object_cache.find(name);
return it != _object_cache.end()
       ? it->second
       : _object_cache.emplace(name, new Object).first->second;

答案 2 :(得分:2)

std :: unordered_map上find的复杂性是O(1)(常量),特别是std :: string键具有良好的散列,导致非常低的冲突率。即使方法的名称是find,它也不会像你指出的那样进行线性扫描。

如果你想进行某种缓存,这个容器绝对是一个好的开始。

答案 3 :(得分:0)

请注意,cache通常不仅是快速O(1)访问权限,还包含有界数据结构。当添加越来越多的元素时,std::unordered_map将动态增加其大小。当资源有限时(例如,将大量文件从磁盘读入内存),您需要一个有限且快速的数据结构来提高系统的响应能力。

相比之下,cache只要size()到达capacity(),就会使用逐出策略,方法是替换价值最低的元素。

您可以在cache之上实施std::unordered_map。然后可以通过重新定义insert()成员来实施驱逐策略。如果您想要N - 方式(对于小而且固定的N)关联缓存(即一个项目最多可以替换N个其他项目),您可以使用{{ 1}}接口替换其中一个桶的条目。

对于完全关联的缓存(即任何项目可以替换任何其他项目),您可以通过添加bucket()作为辅助数据结构来使用最近最少使用的驱逐策略:

std::list

通过将这两个结构包装在using key_tracker_type = std::list<K>; using key_to_value_type = std::unordered_map< K,std::pair<V,typename key_tracker_type::iterator> >; 类中,您可以定义cache以在容量已满时触发替换。发生这种情况时,您insert()最近最少使用的项目和pop_front()当前项目进入列表。

Tim Day's blog there is an extensive example上使用完整源代码实现上述缓存数据结构。它的实现也可以使用Boost.Bimap或Boost.MultiIndex有效地完成。

答案 4 :(得分:0)

map / unordered_map的insert / emplace接口足以完成您想要的操作:找到位置,并在必要时插入。由于此处的映射值是指针,因此ekatmur的响应是理想的。如果你的值是地图中完全成熟的对象而不是指针,你可以使用这样的东西:

Object& ResourceManager::object_named(const char *name, const Object& initialValue) {
    return _object_cache.emplace(name, initialValue).first->second;
}

如果没有与name具有相同值的键,则值initialValuename构成需要插入的键值对的参数。 emplace返回一对,second表示是否插入了任何内容(name中的密钥是新密钥) - 我们在此不关心;并且first是指向(可能是新创建的)键值对条目的迭代器,其键值等于name的值。因此,如果密钥已经存在,则取消引用first会为密钥提供原始Ojbect,而该密钥尚未被initialValue覆盖;否则,使用name的值重新插入密钥,并从initialValue复制条目的值部分,first指向该值。

ekatmur的回答相当于:

Object& ResourceManager::object_named(const char *name) {
    bool res;
    auto iter = _object_cache.end();
    std::tie(iter, res) = _object_cache.emplace(name, nullptr);
    if (res) {
        iter->second = new Object(); // we inserted a null pointer - now replace it
    }
    return iter->second;
}

但是由于operator[]创建的默认构造指针值为null而无法确定是否需要分配新的Object。它更简洁,更容易阅读。