blob: f615a32b19b75d45a0a7c2e29fba1851805ff84e [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
Kenny Root0ddfe112013-09-11 23:41:23 -070020#include <UniquePtr.h>
Raph Levienb6ea1752012-10-25 23:11:13 -070021#include <utils/BasicHashtable.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();
John Reck9d8707c2014-04-11 19:14:15 -070051 const TValue& peekOldestValue();
Raph Levienb6ea1752012-10-25 23:11:13 -070052
Romain Guy5ca402a2012-11-28 18:26:54 -080053 class Iterator {
54 public:
55 Iterator(const LruCache<TKey, TValue>& cache): mCache(cache), mIndex(-1) {
56 }
57
58 bool next() {
59 mIndex = mCache.mTable->next(mIndex);
60 return mIndex != -1;
61 }
62
63 size_t index() const {
64 return mIndex;
65 }
66
67 const TValue& value() const {
68 return mCache.mTable->entryAt(mIndex).value;
69 }
70
71 const TKey& key() const {
72 return mCache.mTable->entryAt(mIndex).key;
73 }
74 private:
75 const LruCache<TKey, TValue>& mCache;
76 size_t mIndex;
77 };
78
Raph Levienb6ea1752012-10-25 23:11:13 -070079private:
80 LruCache(const LruCache& that); // disallow copy constructor
81
82 struct Entry {
83 TKey key;
84 TValue value;
85 Entry* parent;
86 Entry* child;
87
88 Entry(TKey key_, TValue value_) : key(key_), value(value_), parent(NULL), child(NULL) {
89 }
90 const TKey& getKey() const { return key; }
91 };
92
93 void attachToCache(Entry& entry);
94 void detachFromCache(Entry& entry);
95 void rehash(size_t newCapacity);
96
97 UniquePtr<BasicHashtable<TKey, Entry> > mTable;
98 OnEntryRemoved<TKey, TValue>* mListener;
99 Entry* mOldest;
100 Entry* mYoungest;
101 uint32_t mMaxCapacity;
102 TValue mNullValue;
103};
104
105// Implementation is here, because it's fully templated
106template <typename TKey, typename TValue>
107LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity): mMaxCapacity(maxCapacity),
108 mNullValue(NULL), mTable(new BasicHashtable<TKey, Entry>), mYoungest(NULL), mOldest(NULL),
109 mListener(NULL) {
110};
111
112template<typename K, typename V>
113void LruCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) {
114 mListener = listener;
115}
116
117template <typename TKey, typename TValue>
118size_t LruCache<TKey, TValue>::size() const {
119 return mTable->size();
120}
121
122template <typename TKey, typename TValue>
123const TValue& LruCache<TKey, TValue>::get(const TKey& key) {
124 hash_t hash = hash_type(key);
125 ssize_t index = mTable->find(-1, hash, key);
126 if (index == -1) {
127 return mNullValue;
128 }
129 Entry& entry = mTable->editEntryAt(index);
130 detachFromCache(entry);
131 attachToCache(entry);
132 return entry.value;
133}
134
135template <typename TKey, typename TValue>
136bool LruCache<TKey, TValue>::put(const TKey& key, const TValue& value) {
137 if (mMaxCapacity != kUnlimitedCapacity && size() >= mMaxCapacity) {
138 removeOldest();
139 }
140
141 hash_t hash = hash_type(key);
142 ssize_t index = mTable->find(-1, hash, key);
143 if (index >= 0) {
144 return false;
145 }
146 if (!mTable->hasMoreRoom()) {
147 rehash(mTable->capacity() * 2);
148 }
149
150 // Would it be better to initialize a blank entry and assign key, value?
151 Entry initEntry(key, value);
152 index = mTable->add(hash, initEntry);
153 Entry& entry = mTable->editEntryAt(index);
154 attachToCache(entry);
155 return true;
156}
157
158template <typename TKey, typename TValue>
159bool LruCache<TKey, TValue>::remove(const TKey& key) {
160 hash_t hash = hash_type(key);
161 ssize_t index = mTable->find(-1, hash, key);
162 if (index < 0) {
163 return false;
164 }
165 Entry& entry = mTable->editEntryAt(index);
166 if (mListener) {
167 (*mListener)(entry.key, entry.value);
168 }
169 detachFromCache(entry);
170 mTable->removeAt(index);
171 return true;
172}
173
174template <typename TKey, typename TValue>
175bool LruCache<TKey, TValue>::removeOldest() {
176 if (mOldest != NULL) {
177 return remove(mOldest->key);
178 // TODO: should probably abort if false
179 }
180 return false;
181}
182
183template <typename TKey, typename TValue>
John Reck9d8707c2014-04-11 19:14:15 -0700184const TValue& LruCache<TKey, TValue>::peekOldestValue() {
185 if (mOldest) {
186 return mOldest->value;
187 }
188 return mNullValue;
189}
190
191template <typename TKey, typename TValue>
Raph Levienb6ea1752012-10-25 23:11:13 -0700192void LruCache<TKey, TValue>::clear() {
193 if (mListener) {
194 for (Entry* p = mOldest; p != NULL; p = p->child) {
195 (*mListener)(p->key, p->value);
196 }
197 }
198 mYoungest = NULL;
199 mOldest = NULL;
200 mTable->clear();
201}
202
203template <typename TKey, typename TValue>
204void LruCache<TKey, TValue>::attachToCache(Entry& entry) {
205 if (mYoungest == NULL) {
206 mYoungest = mOldest = &entry;
207 } else {
208 entry.parent = mYoungest;
209 mYoungest->child = &entry;
210 mYoungest = &entry;
211 }
212}
213
214template <typename TKey, typename TValue>
215void LruCache<TKey, TValue>::detachFromCache(Entry& entry) {
216 if (entry.parent != NULL) {
217 entry.parent->child = entry.child;
218 } else {
219 mOldest = entry.child;
220 }
221 if (entry.child != NULL) {
222 entry.child->parent = entry.parent;
223 } else {
224 mYoungest = entry.parent;
225 }
226
227 entry.parent = NULL;
228 entry.child = NULL;
229}
230
231template <typename TKey, typename TValue>
232void LruCache<TKey, TValue>::rehash(size_t newCapacity) {
233 UniquePtr<BasicHashtable<TKey, Entry> > oldTable(mTable.release());
234 Entry* oldest = mOldest;
235
236 mOldest = NULL;
237 mYoungest = NULL;
238 mTable.reset(new BasicHashtable<TKey, Entry>(newCapacity));
239 for (Entry* p = oldest; p != NULL; p = p->child) {
240 put(p->key, p->value);
241 }
242}
243
244}
Romain Guyb3176ac2012-11-28 12:59:40 -0800245
246#endif // ANDROID_UTILS_LRU_CACHE_H