A LinkedHashLruCache implements least recently used caching where it supports access order lru cache eviction while allowing entry level expiration time. When the cache reaches max capacity, LruCache try to remove up to one already expired entries. If it doesn't find any expired entries, it will rem
| 40 | * {@link #cleanupExpiredEntries()} (e.g., via a recurring timer). |
| 41 | */ |
| 42 | abstract class LinkedHashLruCache<K, V> implements LruCache<K, V> { |
| 43 | |
| 44 | private final LinkedHashMap<K, SizedValue> delegate; |
| 45 | private final Ticker ticker; |
| 46 | @Nullable |
| 47 | private final EvictionListener<K, V> evictionListener; |
| 48 | private long estimatedSizeBytes; |
| 49 | private long estimatedMaxSizeBytes; |
| 50 | |
| 51 | LinkedHashLruCache( |
| 52 | final long estimatedMaxSizeBytes, |
| 53 | @Nullable final EvictionListener<K, V> evictionListener, |
| 54 | final Ticker ticker) { |
| 55 | checkState(estimatedMaxSizeBytes > 0, "max estimated cache size should be positive"); |
| 56 | this.estimatedMaxSizeBytes = estimatedMaxSizeBytes; |
| 57 | this.evictionListener = evictionListener; |
| 58 | this.ticker = checkNotNull(ticker, "ticker"); |
| 59 | delegate = new LinkedHashMap<K, SizedValue>( |
| 60 | // rough estimate or minimum hashmap default |
| 61 | Math.max((int) (estimatedMaxSizeBytes / 1000), 16), |
| 62 | /* loadFactor= */ 0.75f, |
| 63 | /* accessOrder= */ true) { |
| 64 | @Override |
| 65 | protected boolean removeEldestEntry(Map.Entry<K, SizedValue> eldest) { |
| 66 | if (estimatedSizeBytes <= LinkedHashLruCache.this.estimatedMaxSizeBytes) { |
| 67 | return false; |
| 68 | } |
| 69 | |
| 70 | // first, remove at most 1 expired entry |
| 71 | boolean removed = cleanupExpiredEntries(1, ticker.read()); |
| 72 | // handles size based eviction if necessary no expired entry |
| 73 | boolean shouldRemove = !removed |
| 74 | && shouldInvalidateEldestEntry(eldest.getKey(), eldest.getValue().value, ticker.read()); |
| 75 | if (shouldRemove) { |
| 76 | // remove entry by us to make sure lruIterator and cache is in sync |
| 77 | LinkedHashLruCache.this.invalidate(eldest.getKey(), EvictionType.SIZE); |
| 78 | } |
| 79 | return false; |
| 80 | } |
| 81 | }; |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Determines if the eldest entry should be kept or not when the cache size limit is reached. Note |
| 86 | * that LruCache is access level and the eldest is determined by access pattern. |
| 87 | */ |
| 88 | @SuppressWarnings("unused") |
| 89 | protected boolean shouldInvalidateEldestEntry(K eldestKey, V eldestValue, long now) { |
| 90 | return true; |
| 91 | } |
| 92 | |
| 93 | /** Determines if the entry is already expired or not. */ |
| 94 | protected abstract boolean isExpired(K key, V value, long nowNanos); |
| 95 | |
| 96 | /** |
| 97 | * Returns estimated size of entry to keep track. If it always returns 1, the max size bytes |
| 98 | * behaves like max number of entry (default behavior). |
| 99 | */ |
nothing calls this directly
no outgoing calls
no test coverage detected