blob: 302b929c7dc642c27ed270e476e5b0a0f34a2cb0 [file] [log] [blame]
Raph Levien8185e472012-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 Guy98fc88d2012-11-28 12:59:40 -080017#ifndef ANDROID_UTILS_LRU_CACHE_H
18#define ANDROID_UTILS_LRU_CACHE_H
19
Raph Levien8185e472012-10-25 23:11:13 -070020#include <utils/BasicHashtable.h>
21#include <utils/GenerationCache.h>
22#include <utils/UniquePtr.h>
23
24namespace android {
25
26// OnEntryRemoved is defined in GenerationCache.h, but maybe should move here.
27
28template <typename TKey, typename TValue>
29class LruCache {
30public:
31 explicit LruCache(uint32_t maxCapacity);
32
33 enum Capacity {
34 kUnlimitedCapacity,
35 };
36
37 void setOnEntryRemovedListener(OnEntryRemoved<TKey, TValue>* listener);
38 size_t size() const;
39 const TValue& get(const TKey& key);
40 bool put(const TKey& key, const TValue& value);
41 bool remove(const TKey& key);
42 bool removeOldest();
43 void clear();
44
Romain Guyf1951df2012-11-28 18:26:54 -080045 class Iterator {
46 public:
47 Iterator(const LruCache<TKey, TValue>& cache): mCache(cache), mIndex(-1) {
48 }
49
50 bool next() {
51 mIndex = mCache.mTable->next(mIndex);
52 return mIndex != -1;
53 }
54
55 size_t index() const {
56 return mIndex;
57 }
58
59 const TValue& value() const {
60 return mCache.mTable->entryAt(mIndex).value;
61 }
62
63 const TKey& key() const {
64 return mCache.mTable->entryAt(mIndex).key;
65 }
66 private:
67 const LruCache<TKey, TValue>& mCache;
68 size_t mIndex;
69 };
70
Raph Levien8185e472012-10-25 23:11:13 -070071private:
72 LruCache(const LruCache& that); // disallow copy constructor
73
74 struct Entry {
75 TKey key;
76 TValue value;
77 Entry* parent;
78 Entry* child;
79
80 Entry(TKey key_, TValue value_) : key(key_), value(value_), parent(NULL), child(NULL) {
81 }
82 const TKey& getKey() const { return key; }
83 };
84
85 void attachToCache(Entry& entry);
86 void detachFromCache(Entry& entry);
87 void rehash(size_t newCapacity);
88
89 UniquePtr<BasicHashtable<TKey, Entry> > mTable;
90 OnEntryRemoved<TKey, TValue>* mListener;
91 Entry* mOldest;
92 Entry* mYoungest;
93 uint32_t mMaxCapacity;
94 TValue mNullValue;
95};
96
97// Implementation is here, because it's fully templated
98template <typename TKey, typename TValue>
99LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity): mMaxCapacity(maxCapacity),
100 mNullValue(NULL), mTable(new BasicHashtable<TKey, Entry>), mYoungest(NULL), mOldest(NULL),
101 mListener(NULL) {
102};
103
104template<typename K, typename V>
105void LruCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) {
106 mListener = listener;
107}
108
109template <typename TKey, typename TValue>
110size_t LruCache<TKey, TValue>::size() const {
111 return mTable->size();
112}
113
114template <typename TKey, typename TValue>
115const TValue& LruCache<TKey, TValue>::get(const TKey& key) {
116 hash_t hash = hash_type(key);
117 ssize_t index = mTable->find(-1, hash, key);
118 if (index == -1) {
119 return mNullValue;
120 }
121 Entry& entry = mTable->editEntryAt(index);
122 detachFromCache(entry);
123 attachToCache(entry);
124 return entry.value;
125}
126
127template <typename TKey, typename TValue>
128bool LruCache<TKey, TValue>::put(const TKey& key, const TValue& value) {
129 if (mMaxCapacity != kUnlimitedCapacity && size() >= mMaxCapacity) {
130 removeOldest();
131 }
132
133 hash_t hash = hash_type(key);
134 ssize_t index = mTable->find(-1, hash, key);
135 if (index >= 0) {
136 return false;
137 }
138 if (!mTable->hasMoreRoom()) {
139 rehash(mTable->capacity() * 2);
140 }
141
142 // Would it be better to initialize a blank entry and assign key, value?
143 Entry initEntry(key, value);
144 index = mTable->add(hash, initEntry);
145 Entry& entry = mTable->editEntryAt(index);
146 attachToCache(entry);
147 return true;
148}
149
150template <typename TKey, typename TValue>
151bool LruCache<TKey, TValue>::remove(const TKey& key) {
152 hash_t hash = hash_type(key);
153 ssize_t index = mTable->find(-1, hash, key);
154 if (index < 0) {
155 return false;
156 }
157 Entry& entry = mTable->editEntryAt(index);
158 if (mListener) {
159 (*mListener)(entry.key, entry.value);
160 }
161 detachFromCache(entry);
162 mTable->removeAt(index);
163 return true;
164}
165
166template <typename TKey, typename TValue>
167bool LruCache<TKey, TValue>::removeOldest() {
168 if (mOldest != NULL) {
169 return remove(mOldest->key);
170 // TODO: should probably abort if false
171 }
172 return false;
173}
174
175template <typename TKey, typename TValue>
176void LruCache<TKey, TValue>::clear() {
177 if (mListener) {
178 for (Entry* p = mOldest; p != NULL; p = p->child) {
179 (*mListener)(p->key, p->value);
180 }
181 }
182 mYoungest = NULL;
183 mOldest = NULL;
184 mTable->clear();
185}
186
187template <typename TKey, typename TValue>
188void LruCache<TKey, TValue>::attachToCache(Entry& entry) {
189 if (mYoungest == NULL) {
190 mYoungest = mOldest = &entry;
191 } else {
192 entry.parent = mYoungest;
193 mYoungest->child = &entry;
194 mYoungest = &entry;
195 }
196}
197
198template <typename TKey, typename TValue>
199void LruCache<TKey, TValue>::detachFromCache(Entry& entry) {
200 if (entry.parent != NULL) {
201 entry.parent->child = entry.child;
202 } else {
203 mOldest = entry.child;
204 }
205 if (entry.child != NULL) {
206 entry.child->parent = entry.parent;
207 } else {
208 mYoungest = entry.parent;
209 }
210
211 entry.parent = NULL;
212 entry.child = NULL;
213}
214
215template <typename TKey, typename TValue>
216void LruCache<TKey, TValue>::rehash(size_t newCapacity) {
217 UniquePtr<BasicHashtable<TKey, Entry> > oldTable(mTable.release());
218 Entry* oldest = mOldest;
219
220 mOldest = NULL;
221 mYoungest = NULL;
222 mTable.reset(new BasicHashtable<TKey, Entry>(newCapacity));
223 for (Entry* p = oldest; p != NULL; p = p->child) {
224 put(p->key, p->value);
225 }
226}
227
228}
Romain Guy98fc88d2012-11-28 12:59:40 -0800229
230#endif // ANDROID_UTILS_LRU_CACHE_H