// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file contains a template for a Most Recently Used cache that allows // constant-time access to items using a key, but easy identification of the // least-recently-used items for removal. Each key can only be associated with // one payload item at a time. // // The key object will be stored twice, so it should support efficient copying. // // NOTE: While all operations are O(1), this code is written for // legibility rather than optimality. If future profiling identifies this as // a bottleneck, there is room for smaller values of 1 in the O(1). :] #ifndef BASE_CONTAINERS_MRU_CACHE_H_ #define BASE_CONTAINERS_MRU_CACHE_H_ #include <list> #include <map> #include <utility> #include "base/basictypes.h" #include "base/containers/hash_tables.h" #include "base/logging.h" namespace base { // MRUCacheBase ---------------------------------------------------------------- // This template is used to standardize map type containers that can be used // by MRUCacheBase. This level of indirection is necessary because of the way // that template template params and default template params interact. template <class KeyType, class ValueType> struct MRUCacheStandardMap { typedef std::map<KeyType, ValueType> Type; }; // Base class for the MRU cache specializations defined below. // The deletor will get called on all payloads that are being removed or // replaced. template <class KeyType, class PayloadType, class DeletorType, template <typename, typename> class MapType = MRUCacheStandardMap> class MRUCacheBase { public: // The payload of the list. This maintains a copy of the key so we can // efficiently delete things given an element of the list. typedef std::pair<KeyType, PayloadType> value_type; private: typedef std::list<value_type> PayloadList; typedef typename MapType<KeyType, typename PayloadList::iterator>::Type KeyIndex; public: typedef typename PayloadList::size_type size_type; typedef typename PayloadList::iterator iterator; typedef typename PayloadList::const_iterator const_iterator; typedef typename PayloadList::reverse_iterator reverse_iterator; typedef typename PayloadList::const_reverse_iterator const_reverse_iterator; enum { NO_AUTO_EVICT = 0 }; // The max_size is the size at which the cache will prune its members to when // a new item is inserted. If the caller wants to manager this itself (for // example, maybe it has special work to do when something is evicted), it // can pass NO_AUTO_EVICT to not restrict the cache size. explicit MRUCacheBase(size_type max_size) : max_size_(max_size) { } MRUCacheBase(size_type max_size, const DeletorType& deletor) : max_size_(max_size), deletor_(deletor) { } virtual ~MRUCacheBase() { iterator i = begin(); while (i != end()) i = Erase(i); } size_type max_size() const { return max_size_; } // Inserts a payload item with the given key. If an existing item has // the same key, it is removed prior to insertion. An iterator indicating the // inserted item will be returned (this will always be the front of the list). // // The payload will be copied. In the case of an OwningMRUCache, this function // will take ownership of the pointer. iterator Put(const KeyType& key, const PayloadType& payload) { // Remove any existing payload with that key. typename KeyIndex::iterator index_iter = index_.find(key); if (index_iter != index_.end()) { // Erase the reference to it. This will call the deletor on the removed // element. The index reference will be replaced in the code below. Erase(index_iter->second); } else if (max_size_ != NO_AUTO_EVICT) { // New item is being inserted which might make it larger than the maximum // size: kick the oldest thing out if necessary. ShrinkToSize(max_size_ - 1); } ordering_.push_front(value_type(key, payload)); index_.insert(std::make_pair(key, ordering_.begin())); return ordering_.begin(); } // Retrieves the contents of the given key, or end() if not found. This method // has the side effect of moving the requested item to the front of the // recency list. // // TODO(brettw) We may want a const version of this function in the future. iterator Get(const KeyType& key) { typename KeyIndex::iterator index_iter = index_.find(key); if (index_iter == index_.end()) return end(); typename PayloadList::iterator iter = index_iter->second; // Move the touched item to the front of the recency ordering. ordering_.splice(ordering_.begin(), ordering_, iter); return ordering_.begin(); } // Retrieves the payload associated with a given key and returns it via // result without affecting the ordering (unlike Get). iterator Peek(const KeyType& key) { typename KeyIndex::const_iterator index_iter = index_.find(key); if (index_iter == index_.end()) return end(); return index_iter->second; } const_iterator Peek(const KeyType& key) const { typename KeyIndex::const_iterator index_iter = index_.find(key); if (index_iter == index_.end()) return end(); return index_iter->second; } // Erases the item referenced by the given iterator. An iterator to the item // following it will be returned. The iterator must be valid. iterator Erase(iterator pos) { deletor_(pos->second); index_.erase(pos->first); return ordering_.erase(pos); } // MRUCache entries are often processed in reverse order, so we add this // convenience function (not typically defined by STL containers). reverse_iterator Erase(reverse_iterator pos) { // We have to actually give it the incremented iterator to delete, since // the forward iterator that base() returns is actually one past the item // being iterated over. return reverse_iterator(Erase((++pos).base())); } // Shrinks the cache so it only holds |new_size| items. If |new_size| is // bigger or equal to the current number of items, this will do nothing. void ShrinkToSize(size_type new_size) { for (size_type i = size(); i > new_size; i--) Erase(rbegin()); } // Deletes everything from the cache. void Clear() { for (typename PayloadList::iterator i(ordering_.begin()); i != ordering_.end(); ++i) deletor_(i->second); index_.clear(); ordering_.clear(); } // Returns the number of elements in the cache. size_type size() const { // We don't use ordering_.size() for the return value because // (as a linked list) it can be O(n). DCHECK(index_.size() == ordering_.size()); return index_.size(); } // Allows iteration over the list. Forward iteration starts with the most // recent item and works backwards. // // Note that since these iterators are actually iterators over a list, you // can keep them as you insert or delete things (as long as you don't delete // the one you are pointing to) and they will still be valid. iterator begin() { return ordering_.begin(); } const_iterator begin() const { return ordering_.begin(); } iterator end() { return ordering_.end(); } const_iterator end() const { return ordering_.end(); } reverse_iterator rbegin() { return ordering_.rbegin(); } const_reverse_iterator rbegin() const { return ordering_.rbegin(); } reverse_iterator rend() { return ordering_.rend(); } const_reverse_iterator rend() const { return ordering_.rend(); } bool empty() const { return ordering_.empty(); } private: PayloadList ordering_; KeyIndex index_; size_type max_size_; DeletorType deletor_; DISALLOW_COPY_AND_ASSIGN(MRUCacheBase); }; // MRUCache -------------------------------------------------------------------- // A functor that does nothing. Used by the MRUCache. template<class PayloadType> class MRUCacheNullDeletor { public: void operator()(PayloadType& payload) { } }; // A container that does not do anything to free its data. Use this when storing // value types (as opposed to pointers) in the list. template <class KeyType, class PayloadType> class MRUCache : public MRUCacheBase<KeyType, PayloadType, MRUCacheNullDeletor<PayloadType> > { private: typedef MRUCacheBase<KeyType, PayloadType, MRUCacheNullDeletor<PayloadType> > ParentType; public: // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. explicit MRUCache(typename ParentType::size_type max_size) : ParentType(max_size) { } virtual ~MRUCache() { } private: DISALLOW_COPY_AND_ASSIGN(MRUCache); }; // OwningMRUCache -------------------------------------------------------------- template<class PayloadType> class MRUCachePointerDeletor { public: void operator()(PayloadType& payload) { delete payload; } }; // A cache that owns the payload type, which must be a non-const pointer type. // The pointers will be deleted when they are removed, replaced, or when the // cache is destroyed. template <class KeyType, class PayloadType> class OwningMRUCache : public MRUCacheBase<KeyType, PayloadType, MRUCachePointerDeletor<PayloadType> > { private: typedef MRUCacheBase<KeyType, PayloadType, MRUCachePointerDeletor<PayloadType> > ParentType; public: // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. explicit OwningMRUCache(typename ParentType::size_type max_size) : ParentType(max_size) { } virtual ~OwningMRUCache() { } private: DISALLOW_COPY_AND_ASSIGN(OwningMRUCache); }; // HashingMRUCache ------------------------------------------------------------ template <class KeyType, class ValueType> struct MRUCacheHashMap { typedef base::hash_map<KeyType, ValueType> Type; }; // This class is similar to MRUCache, except that it uses base::hash_map as // the map type instead of std::map. Note that your KeyType must be hashable // to use this cache. template <class KeyType, class PayloadType> class HashingMRUCache : public MRUCacheBase<KeyType, PayloadType, MRUCacheNullDeletor<PayloadType>, MRUCacheHashMap> { private: typedef MRUCacheBase<KeyType, PayloadType, MRUCacheNullDeletor<PayloadType>, MRUCacheHashMap> ParentType; public: // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. explicit HashingMRUCache(typename ParentType::size_type max_size) : ParentType(max_size) { } virtual ~HashingMRUCache() { } private: DISALLOW_COPY_AND_ASSIGN(HashingMRUCache); }; } // namespace base #endif // BASE_CONTAINERS_MRU_CACHE_H_