You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
298 lines
8.4 KiB
298 lines
8.4 KiB
/* |
|
* 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 <memory> |
|
#include <unordered_set> |
|
|
|
#include "utils/TypeHelpers.h" // hash_t |
|
|
|
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); |
|
virtual ~LruCache(); |
|
|
|
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(); |
|
|
|
private: |
|
LruCache(const LruCache& that); // disallow copy constructor |
|
|
|
// Super class so that we can have entries having only a key reference, for searches. |
|
class KeyedEntry { |
|
public: |
|
virtual const TKey& getKey() const = 0; |
|
// Make sure the right destructor is executed so that keys and values are deleted. |
|
virtual ~KeyedEntry() {} |
|
}; |
|
|
|
class Entry final : public KeyedEntry { |
|
public: |
|
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 final { return key; } |
|
}; |
|
|
|
class EntryForSearch : public KeyedEntry { |
|
public: |
|
const TKey& key; |
|
EntryForSearch(const TKey& key_) : key(key_) { |
|
} |
|
const TKey& getKey() const final { return key; } |
|
}; |
|
|
|
struct HashForEntry : public std::unary_function<KeyedEntry*, hash_t> { |
|
size_t operator() (const KeyedEntry* entry) const { |
|
return hash_type(entry->getKey()); |
|
}; |
|
}; |
|
|
|
struct EqualityForHashedEntries : public std::unary_function<KeyedEntry*, hash_t> { |
|
bool operator() (const KeyedEntry* lhs, const KeyedEntry* rhs) const { |
|
return lhs->getKey() == rhs->getKey(); |
|
}; |
|
}; |
|
|
|
// All entries in the set will be Entry*. Using the weaker KeyedEntry as to allow entries |
|
// that have only a key reference, for searching. |
|
typedef std::unordered_set<KeyedEntry*, HashForEntry, EqualityForHashedEntries> LruCacheSet; |
|
|
|
void attachToCache(Entry& entry); |
|
void detachFromCache(Entry& entry); |
|
|
|
typename LruCacheSet::iterator findByKey(const TKey& key) { |
|
EntryForSearch entryForSearch(key); |
|
typename LruCacheSet::iterator result = mSet->find(&entryForSearch); |
|
return result; |
|
} |
|
|
|
std::unique_ptr<LruCacheSet> mSet; |
|
OnEntryRemoved<TKey, TValue>* mListener; |
|
Entry* mOldest; |
|
Entry* mYoungest; |
|
uint32_t mMaxCapacity; |
|
TValue mNullValue; |
|
|
|
public: |
|
// To be used like: |
|
// while (it.next()) { |
|
// it.value(); it.key(); |
|
// } |
|
class Iterator { |
|
public: |
|
Iterator(const LruCache<TKey, TValue>& cache): |
|
mCache(cache), mIterator(mCache.mSet->begin()), mBeginReturned(false) { |
|
} |
|
|
|
bool next() { |
|
if (mIterator == mCache.mSet->end()) { |
|
return false; |
|
} |
|
if (!mBeginReturned) { |
|
// mIterator has been initialized to the beginning and |
|
// hasn't been returned. Do not advance: |
|
mBeginReturned = true; |
|
} else { |
|
std::advance(mIterator, 1); |
|
} |
|
bool ret = (mIterator != mCache.mSet->end()); |
|
return ret; |
|
} |
|
|
|
const TValue& value() const { |
|
// All the elements in the set are of type Entry. See comment in the definition |
|
// of LruCacheSet above. |
|
return reinterpret_cast<Entry *>(*mIterator)->value; |
|
} |
|
|
|
const TKey& key() const { |
|
return (*mIterator)->getKey(); |
|
} |
|
private: |
|
const LruCache<TKey, TValue>& mCache; |
|
typename LruCacheSet::iterator mIterator; |
|
bool mBeginReturned; |
|
}; |
|
}; |
|
|
|
// Implementation is here, because it's fully templated |
|
template <typename TKey, typename TValue> |
|
LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity) |
|
: mSet(new LruCacheSet()) |
|
, mListener(NULL) |
|
, mOldest(NULL) |
|
, mYoungest(NULL) |
|
, mMaxCapacity(maxCapacity) |
|
, mNullValue(0) { |
|
mSet->max_load_factor(1.0); |
|
}; |
|
|
|
template <typename TKey, typename TValue> |
|
LruCache<TKey, TValue>::~LruCache() { |
|
// Need to delete created entries. |
|
clear(); |
|
}; |
|
|
|
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 mSet->size(); |
|
} |
|
|
|
template <typename TKey, typename TValue> |
|
const TValue& LruCache<TKey, TValue>::get(const TKey& key) { |
|
typename LruCacheSet::const_iterator find_result = findByKey(key); |
|
if (find_result == mSet->end()) { |
|
return mNullValue; |
|
} |
|
// All the elements in the set are of type Entry. See comment in the definition |
|
// of LruCacheSet above. |
|
Entry *entry = reinterpret_cast<Entry*>(*find_result); |
|
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(); |
|
} |
|
|
|
if (findByKey(key) != mSet->end()) { |
|
return false; |
|
} |
|
|
|
Entry* newEntry = new Entry(key, value); |
|
mSet->insert(newEntry); |
|
attachToCache(*newEntry); |
|
return true; |
|
} |
|
|
|
template <typename TKey, typename TValue> |
|
bool LruCache<TKey, TValue>::remove(const TKey& key) { |
|
typename LruCacheSet::const_iterator find_result = findByKey(key); |
|
if (find_result == mSet->end()) { |
|
return false; |
|
} |
|
// All the elements in the set are of type Entry. See comment in the definition |
|
// of LruCacheSet above. |
|
Entry* entry = reinterpret_cast<Entry*>(*find_result); |
|
mSet->erase(entry); |
|
if (mListener) { |
|
(*mListener)(entry->key, entry->value); |
|
} |
|
detachFromCache(*entry); |
|
delete entry; |
|
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; |
|
for (auto entry : *mSet.get()) { |
|
delete entry; |
|
} |
|
mSet->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; |
|
} |
|
|
|
} |
|
#endif // ANDROID_UTILS_LRU_CACHE_H
|
|
|