LruCache.h revision 4887842c925f304dc862bdd5810f27cdd2eaedcb
1/*
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
17#ifndef ANDROID_UTILS_LRU_CACHE_H
18#define ANDROID_UTILS_LRU_CACHE_H
19
20#include <utils/BasicHashtable.h>
21#include <utils/UniquePtr.h>
22
23namespace android {
24
25/**
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
34
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
52    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);
59            return (ssize_t)mIndex != -1;
60        }
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
78private:
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>
106LruCache<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) {
113};
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}
240
241#endif // ANDROID_UTILS_LRU_CACHE_H
242