| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef ANDROID_UTILS_LRU_CACHE_H |
| #define ANDROID_UTILS_LRU_CACHE_H |
| |
| #include <UniquePtr.h> |
| #include <utils/BasicHashtable.h> |
| |
| namespace android { |
| |
| /** |
| * GenerationCache callback used when an item is removed |
| */ |
| template<typename EntryKey, typename EntryValue> |
| class OnEntryRemoved { |
| public: |
| virtual ~OnEntryRemoved() { }; |
| virtual void operator()(EntryKey& key, EntryValue& value) = 0; |
| }; // class OnEntryRemoved |
| |
| template <typename TKey, typename TValue> |
| class LruCache { |
| public: |
| explicit LruCache(uint32_t maxCapacity); |
| |
| enum Capacity { |
| kUnlimitedCapacity, |
| }; |
| |
| void setOnEntryRemovedListener(OnEntryRemoved<TKey, TValue>* listener); |
| size_t size() const; |
| const TValue& get(const TKey& key); |
| bool put(const TKey& key, const TValue& value); |
| bool remove(const TKey& key); |
| bool removeOldest(); |
| void clear(); |
| const TValue& peekOldestValue(); |
| |
| class Iterator { |
| public: |
| Iterator(const LruCache<TKey, TValue>& cache): mCache(cache), mIndex(-1) { |
| } |
| |
| bool next() { |
| mIndex = mCache.mTable->next(mIndex); |
| return (ssize_t)mIndex != -1; |
| } |
| |
| size_t index() const { |
| return mIndex; |
| } |
| |
| const TValue& value() const { |
| return mCache.mTable->entryAt(mIndex).value; |
| } |
| |
| const TKey& key() const { |
| return mCache.mTable->entryAt(mIndex).key; |
| } |
| private: |
| const LruCache<TKey, TValue>& mCache; |
| size_t mIndex; |
| }; |
| |
| private: |
| LruCache(const LruCache& that); // disallow copy constructor |
| |
| struct Entry { |
| TKey key; |
| TValue value; |
| Entry* parent; |
| Entry* child; |
| |
| Entry(TKey key_, TValue value_) : key(key_), value(value_), parent(NULL), child(NULL) { |
| } |
| const TKey& getKey() const { return key; } |
| }; |
| |
| void attachToCache(Entry& entry); |
| void detachFromCache(Entry& entry); |
| void rehash(size_t newCapacity); |
| |
| UniquePtr<BasicHashtable<TKey, Entry> > mTable; |
| OnEntryRemoved<TKey, TValue>* mListener; |
| Entry* mOldest; |
| Entry* mYoungest; |
| uint32_t mMaxCapacity; |
| TValue mNullValue; |
| }; |
| |
| // Implementation is here, because it's fully templated |
| template <typename TKey, typename TValue> |
| LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity) |
| : mTable(new BasicHashtable<TKey, Entry>) |
| , mListener(NULL) |
| , mOldest(NULL) |
| , mYoungest(NULL) |
| , mMaxCapacity(maxCapacity) |
| , mNullValue(NULL) { |
| }; |
| |
| template<typename K, typename V> |
| void LruCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) { |
| mListener = listener; |
| } |
| |
| template <typename TKey, typename TValue> |
| size_t LruCache<TKey, TValue>::size() const { |
| return mTable->size(); |
| } |
| |
| template <typename TKey, typename TValue> |
| const TValue& LruCache<TKey, TValue>::get(const TKey& key) { |
| hash_t hash = hash_type(key); |
| ssize_t index = mTable->find(-1, hash, key); |
| if (index == -1) { |
| return mNullValue; |
| } |
| Entry& entry = mTable->editEntryAt(index); |
| detachFromCache(entry); |
| attachToCache(entry); |
| return entry.value; |
| } |
| |
| template <typename TKey, typename TValue> |
| bool LruCache<TKey, TValue>::put(const TKey& key, const TValue& value) { |
| if (mMaxCapacity != kUnlimitedCapacity && size() >= mMaxCapacity) { |
| removeOldest(); |
| } |
| |
| hash_t hash = hash_type(key); |
| ssize_t index = mTable->find(-1, hash, key); |
| if (index >= 0) { |
| return false; |
| } |
| if (!mTable->hasMoreRoom()) { |
| rehash(mTable->capacity() * 2); |
| } |
| |
| // Would it be better to initialize a blank entry and assign key, value? |
| Entry initEntry(key, value); |
| index = mTable->add(hash, initEntry); |
| Entry& entry = mTable->editEntryAt(index); |
| attachToCache(entry); |
| return true; |
| } |
| |
| template <typename TKey, typename TValue> |
| bool LruCache<TKey, TValue>::remove(const TKey& key) { |
| hash_t hash = hash_type(key); |
| ssize_t index = mTable->find(-1, hash, key); |
| if (index < 0) { |
| return false; |
| } |
| Entry& entry = mTable->editEntryAt(index); |
| if (mListener) { |
| (*mListener)(entry.key, entry.value); |
| } |
| detachFromCache(entry); |
| mTable->removeAt(index); |
| return true; |
| } |
| |
| template <typename TKey, typename TValue> |
| bool LruCache<TKey, TValue>::removeOldest() { |
| if (mOldest != NULL) { |
| return remove(mOldest->key); |
| // TODO: should probably abort if false |
| } |
| return false; |
| } |
| |
| template <typename TKey, typename TValue> |
| const TValue& LruCache<TKey, TValue>::peekOldestValue() { |
| if (mOldest) { |
| return mOldest->value; |
| } |
| return mNullValue; |
| } |
| |
| template <typename TKey, typename TValue> |
| void LruCache<TKey, TValue>::clear() { |
| if (mListener) { |
| for (Entry* p = mOldest; p != NULL; p = p->child) { |
| (*mListener)(p->key, p->value); |
| } |
| } |
| mYoungest = NULL; |
| mOldest = NULL; |
| mTable->clear(); |
| } |
| |
| template <typename TKey, typename TValue> |
| void LruCache<TKey, TValue>::attachToCache(Entry& entry) { |
| if (mYoungest == NULL) { |
| mYoungest = mOldest = &entry; |
| } else { |
| entry.parent = mYoungest; |
| mYoungest->child = &entry; |
| mYoungest = &entry; |
| } |
| } |
| |
| template <typename TKey, typename TValue> |
| void LruCache<TKey, TValue>::detachFromCache(Entry& entry) { |
| if (entry.parent != NULL) { |
| entry.parent->child = entry.child; |
| } else { |
| mOldest = entry.child; |
| } |
| if (entry.child != NULL) { |
| entry.child->parent = entry.parent; |
| } else { |
| mYoungest = entry.parent; |
| } |
| |
| entry.parent = NULL; |
| entry.child = NULL; |
| } |
| |
| template <typename TKey, typename TValue> |
| void LruCache<TKey, TValue>::rehash(size_t newCapacity) { |
| UniquePtr<BasicHashtable<TKey, Entry> > oldTable(mTable.release()); |
| Entry* oldest = mOldest; |
| |
| mOldest = NULL; |
| mYoungest = NULL; |
| mTable.reset(new BasicHashtable<TKey, Entry>(newCapacity)); |
| for (Entry* p = oldest; p != NULL; p = p->child) { |
| put(p->key, p->value); |
| } |
| } |
| |
| } |
| |
| #endif // ANDROID_UTILS_LRU_CACHE_H |