LruCache.h revision b3176acd5fa7a35cee7b6722cd5869f13542afdd
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/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 45private: 46 LruCache(const LruCache& that); // disallow copy constructor 47 48 struct Entry { 49 TKey key; 50 TValue value; 51 Entry* parent; 52 Entry* child; 53 54 Entry(TKey key_, TValue value_) : key(key_), value(value_), parent(NULL), child(NULL) { 55 } 56 const TKey& getKey() const { return key; } 57 }; 58 59 void attachToCache(Entry& entry); 60 void detachFromCache(Entry& entry); 61 void rehash(size_t newCapacity); 62 63 UniquePtr<BasicHashtable<TKey, Entry> > mTable; 64 OnEntryRemoved<TKey, TValue>* mListener; 65 Entry* mOldest; 66 Entry* mYoungest; 67 uint32_t mMaxCapacity; 68 TValue mNullValue; 69}; 70 71// Implementation is here, because it's fully templated 72template <typename TKey, typename TValue> 73LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity): mMaxCapacity(maxCapacity), 74 mNullValue(NULL), mTable(new BasicHashtable<TKey, Entry>), mYoungest(NULL), mOldest(NULL), 75 mListener(NULL) { 76}; 77 78template<typename K, typename V> 79void LruCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) { 80 mListener = listener; 81} 82 83template <typename TKey, typename TValue> 84size_t LruCache<TKey, TValue>::size() const { 85 return mTable->size(); 86} 87 88template <typename TKey, typename TValue> 89const TValue& LruCache<TKey, TValue>::get(const TKey& key) { 90 hash_t hash = hash_type(key); 91 ssize_t index = mTable->find(-1, hash, key); 92 if (index == -1) { 93 return mNullValue; 94 } 95 Entry& entry = mTable->editEntryAt(index); 96 detachFromCache(entry); 97 attachToCache(entry); 98 return entry.value; 99} 100 101template <typename TKey, typename TValue> 102bool LruCache<TKey, TValue>::put(const TKey& key, const TValue& value) { 103 if (mMaxCapacity != kUnlimitedCapacity && size() >= mMaxCapacity) { 104 removeOldest(); 105 } 106 107 hash_t hash = hash_type(key); 108 ssize_t index = mTable->find(-1, hash, key); 109 if (index >= 0) { 110 return false; 111 } 112 if (!mTable->hasMoreRoom()) { 113 rehash(mTable->capacity() * 2); 114 } 115 116 // Would it be better to initialize a blank entry and assign key, value? 117 Entry initEntry(key, value); 118 index = mTable->add(hash, initEntry); 119 Entry& entry = mTable->editEntryAt(index); 120 attachToCache(entry); 121 return true; 122} 123 124template <typename TKey, typename TValue> 125bool LruCache<TKey, TValue>::remove(const TKey& key) { 126 hash_t hash = hash_type(key); 127 ssize_t index = mTable->find(-1, hash, key); 128 if (index < 0) { 129 return false; 130 } 131 Entry& entry = mTable->editEntryAt(index); 132 if (mListener) { 133 (*mListener)(entry.key, entry.value); 134 } 135 detachFromCache(entry); 136 mTable->removeAt(index); 137 return true; 138} 139 140template <typename TKey, typename TValue> 141bool LruCache<TKey, TValue>::removeOldest() { 142 if (mOldest != NULL) { 143 return remove(mOldest->key); 144 // TODO: should probably abort if false 145 } 146 return false; 147} 148 149template <typename TKey, typename TValue> 150void LruCache<TKey, TValue>::clear() { 151 if (mListener) { 152 for (Entry* p = mOldest; p != NULL; p = p->child) { 153 (*mListener)(p->key, p->value); 154 } 155 } 156 mYoungest = NULL; 157 mOldest = NULL; 158 mTable->clear(); 159} 160 161template <typename TKey, typename TValue> 162void LruCache<TKey, TValue>::attachToCache(Entry& entry) { 163 if (mYoungest == NULL) { 164 mYoungest = mOldest = &entry; 165 } else { 166 entry.parent = mYoungest; 167 mYoungest->child = &entry; 168 mYoungest = &entry; 169 } 170} 171 172template <typename TKey, typename TValue> 173void LruCache<TKey, TValue>::detachFromCache(Entry& entry) { 174 if (entry.parent != NULL) { 175 entry.parent->child = entry.child; 176 } else { 177 mOldest = entry.child; 178 } 179 if (entry.child != NULL) { 180 entry.child->parent = entry.parent; 181 } else { 182 mYoungest = entry.parent; 183 } 184 185 entry.parent = NULL; 186 entry.child = NULL; 187} 188 189template <typename TKey, typename TValue> 190void LruCache<TKey, TValue>::rehash(size_t newCapacity) { 191 UniquePtr<BasicHashtable<TKey, Entry> > oldTable(mTable.release()); 192 Entry* oldest = mOldest; 193 194 mOldest = NULL; 195 mYoungest = NULL; 196 mTable.reset(new BasicHashtable<TKey, Entry>(newCapacity)); 197 for (Entry* p = oldest; p != NULL; p = p->child) { 198 put(p->key, p->value); 199 } 200} 201 202} 203 204#endif // ANDROID_UTILS_LRU_CACHE_H 205