| // 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/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). |
| // |
| // TODO(brettw) We may want a const version of this function in the future. |
| 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; |
| } |
| |
| // 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_ |