• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // This file contains a template for a Most Recently Used cache that allows
6 // constant-time access to items using a key, but easy identification of the
7 // least-recently-used items for removal.  Each key can only be associated with
8 // one payload item at a time.
9 //
10 // The key object will be stored twice, so it should support efficient copying.
11 //
12 // NOTE: While all operations are O(1), this code is written for
13 // legibility rather than optimality. If future profiling identifies this as
14 // a bottleneck, there is room for smaller values of 1 in the O(1). :]
15 
16 #ifndef BASE_CONTAINERS_MRU_CACHE_H_
17 #define BASE_CONTAINERS_MRU_CACHE_H_
18 
19 #include <list>
20 #include <map>
21 #include <utility>
22 
23 #include "base/basictypes.h"
24 #include "base/containers/hash_tables.h"
25 #include "base/logging.h"
26 
27 namespace base {
28 
29 // MRUCacheBase ----------------------------------------------------------------
30 
31 // This template is used to standardize map type containers that can be used
32 // by MRUCacheBase. This level of indirection is necessary because of the way
33 // that template template params and default template params interact.
34 template <class KeyType, class ValueType>
35 struct MRUCacheStandardMap {
36   typedef std::map<KeyType, ValueType> Type;
37 };
38 
39 // Base class for the MRU cache specializations defined below.
40 // The deletor will get called on all payloads that are being removed or
41 // replaced.
42 template <class KeyType, class PayloadType, class DeletorType,
43           template <typename, typename> class MapType = MRUCacheStandardMap>
44 class MRUCacheBase {
45  public:
46   // The payload of the list. This maintains a copy of the key so we can
47   // efficiently delete things given an element of the list.
48   typedef std::pair<KeyType, PayloadType> value_type;
49 
50  private:
51   typedef std::list<value_type> PayloadList;
52   typedef typename MapType<KeyType,
53                            typename PayloadList::iterator>::Type KeyIndex;
54 
55  public:
56   typedef typename PayloadList::size_type size_type;
57 
58   typedef typename PayloadList::iterator iterator;
59   typedef typename PayloadList::const_iterator const_iterator;
60   typedef typename PayloadList::reverse_iterator reverse_iterator;
61   typedef typename PayloadList::const_reverse_iterator const_reverse_iterator;
62 
63   enum { NO_AUTO_EVICT = 0 };
64 
65   // The max_size is the size at which the cache will prune its members to when
66   // a new item is inserted. If the caller wants to manager this itself (for
67   // example, maybe it has special work to do when something is evicted), it
68   // can pass NO_AUTO_EVICT to not restrict the cache size.
MRUCacheBase(size_type max_size)69   explicit MRUCacheBase(size_type max_size) : max_size_(max_size) {
70   }
71 
MRUCacheBase(size_type max_size,const DeletorType & deletor)72   MRUCacheBase(size_type max_size, const DeletorType& deletor)
73       : max_size_(max_size), deletor_(deletor) {
74   }
75 
~MRUCacheBase()76   virtual ~MRUCacheBase() {
77     iterator i = begin();
78     while (i != end())
79       i = Erase(i);
80   }
81 
max_size()82   size_type max_size() const { return max_size_; }
83 
84   // Inserts a payload item with the given key. If an existing item has
85   // the same key, it is removed prior to insertion. An iterator indicating the
86   // inserted item will be returned (this will always be the front of the list).
87   //
88   // The payload will be copied. In the case of an OwningMRUCache, this function
89   // will take ownership of the pointer.
Put(const KeyType & key,const PayloadType & payload)90   iterator Put(const KeyType& key, const PayloadType& payload) {
91     // Remove any existing payload with that key.
92     typename KeyIndex::iterator index_iter = index_.find(key);
93     if (index_iter != index_.end()) {
94       // Erase the reference to it. This will call the deletor on the removed
95       // element. The index reference will be replaced in the code below.
96       Erase(index_iter->second);
97     } else if (max_size_ != NO_AUTO_EVICT) {
98       // New item is being inserted which might make it larger than the maximum
99       // size: kick the oldest thing out if necessary.
100       ShrinkToSize(max_size_ - 1);
101     }
102 
103     ordering_.push_front(value_type(key, payload));
104     index_.insert(std::make_pair(key, ordering_.begin()));
105     return ordering_.begin();
106   }
107 
108   // Retrieves the contents of the given key, or end() if not found. This method
109   // has the side effect of moving the requested item to the front of the
110   // recency list.
111   //
112   // TODO(brettw) We may want a const version of this function in the future.
Get(const KeyType & key)113   iterator Get(const KeyType& key) {
114     typename KeyIndex::iterator index_iter = index_.find(key);
115     if (index_iter == index_.end())
116       return end();
117     typename PayloadList::iterator iter = index_iter->second;
118 
119     // Move the touched item to the front of the recency ordering.
120     ordering_.splice(ordering_.begin(), ordering_, iter);
121     return ordering_.begin();
122   }
123 
124   // Retrieves the payload associated with a given key and returns it via
125   // result without affecting the ordering (unlike Get).
Peek(const KeyType & key)126   iterator Peek(const KeyType& key) {
127     typename KeyIndex::const_iterator index_iter = index_.find(key);
128     if (index_iter == index_.end())
129       return end();
130     return index_iter->second;
131   }
132 
Peek(const KeyType & key)133   const_iterator Peek(const KeyType& key) const {
134     typename KeyIndex::const_iterator index_iter = index_.find(key);
135     if (index_iter == index_.end())
136       return end();
137     return index_iter->second;
138   }
139 
140   // Erases the item referenced by the given iterator. An iterator to the item
141   // following it will be returned. The iterator must be valid.
Erase(iterator pos)142   iterator Erase(iterator pos) {
143     deletor_(pos->second);
144     index_.erase(pos->first);
145     return ordering_.erase(pos);
146   }
147 
148   // MRUCache entries are often processed in reverse order, so we add this
149   // convenience function (not typically defined by STL containers).
Erase(reverse_iterator pos)150   reverse_iterator Erase(reverse_iterator pos) {
151     // We have to actually give it the incremented iterator to delete, since
152     // the forward iterator that base() returns is actually one past the item
153     // being iterated over.
154     return reverse_iterator(Erase((++pos).base()));
155   }
156 
157   // Shrinks the cache so it only holds |new_size| items. If |new_size| is
158   // bigger or equal to the current number of items, this will do nothing.
ShrinkToSize(size_type new_size)159   void ShrinkToSize(size_type new_size) {
160     for (size_type i = size(); i > new_size; i--)
161       Erase(rbegin());
162   }
163 
164   // Deletes everything from the cache.
Clear()165   void Clear() {
166     for (typename PayloadList::iterator i(ordering_.begin());
167          i != ordering_.end(); ++i)
168       deletor_(i->second);
169     index_.clear();
170     ordering_.clear();
171   }
172 
173   // Returns the number of elements in the cache.
size()174   size_type size() const {
175     // We don't use ordering_.size() for the return value because
176     // (as a linked list) it can be O(n).
177     DCHECK(index_.size() == ordering_.size());
178     return index_.size();
179   }
180 
181   // Allows iteration over the list. Forward iteration starts with the most
182   // recent item and works backwards.
183   //
184   // Note that since these iterators are actually iterators over a list, you
185   // can keep them as you insert or delete things (as long as you don't delete
186   // the one you are pointing to) and they will still be valid.
begin()187   iterator begin() { return ordering_.begin(); }
begin()188   const_iterator begin() const { return ordering_.begin(); }
end()189   iterator end() { return ordering_.end(); }
end()190   const_iterator end() const { return ordering_.end(); }
191 
rbegin()192   reverse_iterator rbegin() { return ordering_.rbegin(); }
rbegin()193   const_reverse_iterator rbegin() const { return ordering_.rbegin(); }
rend()194   reverse_iterator rend() { return ordering_.rend(); }
rend()195   const_reverse_iterator rend() const { return ordering_.rend(); }
196 
empty()197   bool empty() const { return ordering_.empty(); }
198 
199  private:
200   PayloadList ordering_;
201   KeyIndex index_;
202 
203   size_type max_size_;
204 
205   DeletorType deletor_;
206 
207   DISALLOW_COPY_AND_ASSIGN(MRUCacheBase);
208 };
209 
210 // MRUCache --------------------------------------------------------------------
211 
212 // A functor that does nothing. Used by the MRUCache.
213 template<class PayloadType>
214 class MRUCacheNullDeletor {
215  public:
operator()216   void operator()(PayloadType& payload) {
217   }
218 };
219 
220 // A container that does not do anything to free its data. Use this when storing
221 // value types (as opposed to pointers) in the list.
222 template <class KeyType, class PayloadType>
223 class MRUCache : public MRUCacheBase<KeyType,
224                                      PayloadType,
225                                      MRUCacheNullDeletor<PayloadType> > {
226  private:
227   typedef MRUCacheBase<KeyType, PayloadType,
228       MRUCacheNullDeletor<PayloadType> > ParentType;
229 
230  public:
231   // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
MRUCache(typename ParentType::size_type max_size)232   explicit MRUCache(typename ParentType::size_type max_size)
233       : ParentType(max_size) {
234   }
~MRUCache()235   virtual ~MRUCache() {
236   }
237 
238  private:
239   DISALLOW_COPY_AND_ASSIGN(MRUCache);
240 };
241 
242 // OwningMRUCache --------------------------------------------------------------
243 
244 template<class PayloadType>
245 class MRUCachePointerDeletor {
246  public:
operator()247   void operator()(PayloadType& payload) {
248     delete payload;
249   }
250 };
251 
252 // A cache that owns the payload type, which must be a non-const pointer type.
253 // The pointers will be deleted when they are removed, replaced, or when the
254 // cache is destroyed.
255 template <class KeyType, class PayloadType>
256 class OwningMRUCache
257     : public MRUCacheBase<KeyType,
258                           PayloadType,
259                           MRUCachePointerDeletor<PayloadType> > {
260  private:
261   typedef MRUCacheBase<KeyType, PayloadType,
262       MRUCachePointerDeletor<PayloadType> > ParentType;
263 
264  public:
265   // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
OwningMRUCache(typename ParentType::size_type max_size)266   explicit OwningMRUCache(typename ParentType::size_type max_size)
267       : ParentType(max_size) {
268   }
~OwningMRUCache()269   virtual ~OwningMRUCache() {
270   }
271 
272  private:
273   DISALLOW_COPY_AND_ASSIGN(OwningMRUCache);
274 };
275 
276 // HashingMRUCache ------------------------------------------------------------
277 
278 template <class KeyType, class ValueType>
279 struct MRUCacheHashMap {
280   typedef base::hash_map<KeyType, ValueType> Type;
281 };
282 
283 // This class is similar to MRUCache, except that it uses base::hash_map as
284 // the map type instead of std::map. Note that your KeyType must be hashable
285 // to use this cache.
286 template <class KeyType, class PayloadType>
287 class HashingMRUCache : public MRUCacheBase<KeyType,
288                                             PayloadType,
289                                             MRUCacheNullDeletor<PayloadType>,
290                                             MRUCacheHashMap> {
291  private:
292   typedef MRUCacheBase<KeyType, PayloadType,
293                        MRUCacheNullDeletor<PayloadType>,
294                        MRUCacheHashMap> ParentType;
295 
296  public:
297   // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
HashingMRUCache(typename ParentType::size_type max_size)298   explicit HashingMRUCache(typename ParentType::size_type max_size)
299       : ParentType(max_size) {
300   }
~HashingMRUCache()301   virtual ~HashingMRUCache() {
302   }
303 
304  private:
305   DISALLOW_COPY_AND_ASSIGN(HashingMRUCache);
306 };
307 
308 }  // namespace base
309 
310 #endif  // BASE_CONTAINERS_MRU_CACHE_H_
311