blob: 9248ac93fdbb8e852ad30a7e262d3be08d65573f [file] [log] [blame]
Raph Levienb6ea1752012-10-25 23:11:13 -07001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Romain Guyb3176ac2012-11-28 12:59:40 -080017#ifndef ANDROID_UTILS_LRU_CACHE_H
18#define ANDROID_UTILS_LRU_CACHE_H
19
Raph Levienb6ea1752012-10-25 23:11:13 -070020#include <utils/BasicHashtable.h>
Elliott Hughes5c402002014-02-04 23:41:22 +000021#include <utils/UniquePtr.h>
Raph Levienb6ea1752012-10-25 23:11:13 -070022
23namespace android {
24
Mathias Agopian9eb2a3b2013-05-06 20:20:50 -070025/**
26 * GenerationCache callback used when an item is removed
27 */
28template<typename EntryKey, typename EntryValue>
29class OnEntryRemoved {
30public:
31 virtual ~OnEntryRemoved() { };
32 virtual void operator()(EntryKey& key, EntryValue& value) = 0;
33}; // class OnEntryRemoved
Raph Levienb6ea1752012-10-25 23:11:13 -070034
35template <typename TKey, typename TValue>
36class LruCache {
37public:
38 explicit LruCache(uint32_t maxCapacity);
39
40 enum Capacity {
41 kUnlimitedCapacity,
42 };
43
44 void setOnEntryRemovedListener(OnEntryRemoved<TKey, TValue>* listener);
45 size_t size() const;
46 const TValue& get(const TKey& key);
47 bool put(const TKey& key, const TValue& value);
48 bool remove(const TKey& key);
49 bool removeOldest();
50 void clear();
51
Romain Guy5ca402a2012-11-28 18:26:54 -080052 class Iterator {
53 public:
54 Iterator(const LruCache<TKey, TValue>& cache): mCache(cache), mIndex(-1) {
55 }
56
57 bool next() {
58 mIndex = mCache.mTable->next(mIndex);
Mark Salyzyn48878422014-05-22 16:08:52 -070059 return (ssize_t)mIndex != -1;
Romain Guy5ca402a2012-11-28 18:26:54 -080060 }
61
62 size_t index() const {
63 return mIndex;
64 }
65
66 const TValue& value() const {
67 return mCache.mTable->entryAt(mIndex).value;
68 }
69
70 const TKey& key() const {
71 return mCache.mTable->entryAt(mIndex).key;
72 }
73 private:
74 const LruCache<TKey, TValue>& mCache;
75 size_t mIndex;
76 };
77
Raph Levienb6ea1752012-10-25 23:11:13 -070078private:
79 LruCache(const LruCache& that); // disallow copy constructor
80
81 struct Entry {
82 TKey key;
83 TValue value;
84 Entry* parent;
85 Entry* child;
86
87 Entry(TKey key_, TValue value_) : key(key_), value(value_), parent(NULL), child(NULL) {
88 }
89 const TKey& getKey() const { return key; }
90 };
91
92 void attachToCache(Entry& entry);
93 void detachFromCache(Entry& entry);
94 void rehash(size_t newCapacity);
95
96 UniquePtr<BasicHashtable<TKey, Entry> > mTable;
97 OnEntryRemoved<TKey, TValue>* mListener;
98 Entry* mOldest;
99 Entry* mYoungest;
100 uint32_t mMaxCapacity;
101 TValue mNullValue;
102};
103
104// Implementation is here, because it's fully templated
105template <typename TKey, typename TValue>
Mark Salyzyn48878422014-05-22 16:08:52 -0700106LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity)
107 : mTable(new BasicHashtable<TKey, Entry>)
108 , mListener(NULL)
109 , mOldest(NULL)
110 , mYoungest(NULL)
111 , mMaxCapacity(maxCapacity)
112 , mNullValue(NULL) {
Raph Levienb6ea1752012-10-25 23:11:13 -0700113};
114
115template<typename K, typename V>
116void LruCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) {
117 mListener = listener;
118}
119
120template <typename TKey, typename TValue>
121size_t LruCache<TKey, TValue>::size() const {
122 return mTable->size();
123}
124
125template <typename TKey, typename TValue>
126const TValue& LruCache<TKey, TValue>::get(const TKey& key) {
127 hash_t hash = hash_type(key);
128 ssize_t index = mTable->find(-1, hash, key);
129 if (index == -1) {
130 return mNullValue;
131 }
132 Entry& entry = mTable->editEntryAt(index);
133 detachFromCache(entry);
134 attachToCache(entry);
135 return entry.value;
136}
137
138template <typename TKey, typename TValue>
139bool LruCache<TKey, TValue>::put(const TKey& key, const TValue& value) {
140 if (mMaxCapacity != kUnlimitedCapacity && size() >= mMaxCapacity) {
141 removeOldest();
142 }
143
144 hash_t hash = hash_type(key);
145 ssize_t index = mTable->find(-1, hash, key);
146 if (index >= 0) {
147 return false;
148 }
149 if (!mTable->hasMoreRoom()) {
150 rehash(mTable->capacity() * 2);
151 }
152
153 // Would it be better to initialize a blank entry and assign key, value?
154 Entry initEntry(key, value);
155 index = mTable->add(hash, initEntry);
156 Entry& entry = mTable->editEntryAt(index);
157 attachToCache(entry);
158 return true;
159}
160
161template <typename TKey, typename TValue>
162bool LruCache<TKey, TValue>::remove(const TKey& key) {
163 hash_t hash = hash_type(key);
164 ssize_t index = mTable->find(-1, hash, key);
165 if (index < 0) {
166 return false;
167 }
168 Entry& entry = mTable->editEntryAt(index);
169 if (mListener) {
170 (*mListener)(entry.key, entry.value);
171 }
172 detachFromCache(entry);
173 mTable->removeAt(index);
174 return true;
175}
176
177template <typename TKey, typename TValue>
178bool LruCache<TKey, TValue>::removeOldest() {
179 if (mOldest != NULL) {
180 return remove(mOldest->key);
181 // TODO: should probably abort if false
182 }
183 return false;
184}
185
186template <typename TKey, typename TValue>
187void LruCache<TKey, TValue>::clear() {
188 if (mListener) {
189 for (Entry* p = mOldest; p != NULL; p = p->child) {
190 (*mListener)(p->key, p->value);
191 }
192 }
193 mYoungest = NULL;
194 mOldest = NULL;
195 mTable->clear();
196}
197
198template <typename TKey, typename TValue>
199void LruCache<TKey, TValue>::attachToCache(Entry& entry) {
200 if (mYoungest == NULL) {
201 mYoungest = mOldest = &entry;
202 } else {
203 entry.parent = mYoungest;
204 mYoungest->child = &entry;
205 mYoungest = &entry;
206 }
207}
208
209template <typename TKey, typename TValue>
210void LruCache<TKey, TValue>::detachFromCache(Entry& entry) {
211 if (entry.parent != NULL) {
212 entry.parent->child = entry.child;
213 } else {
214 mOldest = entry.child;
215 }
216 if (entry.child != NULL) {
217 entry.child->parent = entry.parent;
218 } else {
219 mYoungest = entry.parent;
220 }
221
222 entry.parent = NULL;
223 entry.child = NULL;
224}
225
226template <typename TKey, typename TValue>
227void LruCache<TKey, TValue>::rehash(size_t newCapacity) {
228 UniquePtr<BasicHashtable<TKey, Entry> > oldTable(mTable.release());
229 Entry* oldest = mOldest;
230
231 mOldest = NULL;
232 mYoungest = NULL;
233 mTable.reset(new BasicHashtable<TKey, Entry>(newCapacity));
234 for (Entry* p = oldest; p != NULL; p = p->child) {
235 put(p->key, p->value);
236 }
237}
238
239}
Romain Guyb3176ac2012-11-28 12:59:40 -0800240
241#endif // ANDROID_UTILS_LRU_CACHE_H