231 lines
6.1 KiB
C++
231 lines
6.1 KiB
C++
/*
|
|
* 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 <utils/BasicHashtable.h>
|
|
#include <utils/GenerationCache.h>
|
|
#include <utils/UniquePtr.h>
|
|
|
|
namespace android {
|
|
|
|
// OnEntryRemoved is defined in GenerationCache.h, but maybe should move here.
|
|
|
|
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();
|
|
|
|
class Iterator {
|
|
public:
|
|
Iterator(const LruCache<TKey, TValue>& cache): mCache(cache), mIndex(-1) {
|
|
}
|
|
|
|
bool next() {
|
|
mIndex = mCache.mTable->next(mIndex);
|
|
return 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): mMaxCapacity(maxCapacity),
|
|
mNullValue(NULL), mTable(new BasicHashtable<TKey, Entry>), mYoungest(NULL), mOldest(NULL),
|
|
mListener(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>
|
|
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
|