Skip to content

Commit 5090b6d

Browse files
sjuddglide-copybara-robot
authored andcommitted
Use a fixed size for items in LruCache.
PiperOrigin-RevId: 303010044
1 parent 5e2ccc6 commit 5090b6d

File tree

2 files changed

+97
-17
lines changed

2 files changed

+97
-17
lines changed

library/src/main/java/com/bumptech/glide/util/LruCache.java

+43-16
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* @param <Y> The type of the values.
1616
*/
1717
public class LruCache<T, Y> {
18-
private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
18+
private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);
1919
private final long initialMaxSize;
2020
private long maxSize;
2121
private long currentSize;
@@ -98,7 +98,8 @@ public synchronized boolean contains(@NonNull T key) {
9898
*/
9999
@Nullable
100100
public synchronized Y get(@NonNull T key) {
101-
return cache.get(key);
101+
Entry<Y> entry = cache.get(key);
102+
return entry != null ? entry.value : null;
102103
}
103104

104105
/**
@@ -109,6 +110,19 @@ public synchronized Y get(@NonNull T key) {
109110
* the cache and instead {@link #onItemEvicted(Object, Object)} will be called synchronously with
110111
* the given key and item.
111112
*
113+
* <p>The size of the item is determined by the {@link #getSize(Object)} method. To avoid errors
114+
* where {@link #getSize(Object)} returns different values for the same object when called at
115+
* different times, the size value is acquired in {@code put} and retained until the item is
116+
* evicted, replaced or removed.
117+
*
118+
* <p>If {@code item} is null the behavior here is a little odd. For the most part it's similar to
119+
* simply calling {@link #remove(Object)} with the given key. The difference is that calling this
120+
* method with a null {@code item} will result in an entry remaining in the cache with a null
121+
* value and 0 size. The only real consequence is that at some point {@link #onItemEvicted(Object,
122+
* Object)} may be called with the given {@code key} and a null value. Ideally we'd make calling
123+
* this method with a null {@code item} identical to {@link #remove(Object)} but we're preserving
124+
* this odd behavior to match older versions :(.
125+
*
112126
* @param key The key to add the item at.
113127
* @param item The item to add.
114128
*/
@@ -123,17 +137,17 @@ public synchronized Y put(@NonNull T key, @Nullable Y item) {
123137
if (item != null) {
124138
currentSize += itemSize;
125139
}
126-
@Nullable final Y old = cache.put(key, item);
140+
@Nullable Entry<Y> old = cache.put(key, item == null ? null : new Entry<>(item, itemSize));
127141
if (old != null) {
128-
currentSize -= getSize(old);
142+
currentSize -= old.size;
129143

130-
if (!old.equals(item)) {
131-
onItemEvicted(key, old);
144+
if (!old.value.equals(item)) {
145+
onItemEvicted(key, old.value);
132146
}
133147
}
134148
evict();
135149

136-
return old;
150+
return old != null ? old.value : null;
137151
}
138152

139153
/**
@@ -143,11 +157,12 @@ public synchronized Y put(@NonNull T key, @Nullable Y item) {
143157
*/
144158
@Nullable
145159
public synchronized Y remove(@NonNull T key) {
146-
final Y value = cache.remove(key);
147-
if (value != null) {
148-
currentSize -= getSize(value);
160+
Entry<Y> entry = cache.remove(key);
161+
if (entry == null) {
162+
return null;
149163
}
150-
return value;
164+
currentSize -= entry.size;
165+
return entry.value;
151166
}
152167

153168
/** Clears all items in the cache. */
@@ -162,20 +177,32 @@ public void clearMemory() {
162177
* @param size The size the cache should be less than.
163178
*/
164179
protected synchronized void trimToSize(long size) {
165-
Map.Entry<T, Y> last;
166-
Iterator<Map.Entry<T, Y>> cacheIterator;
180+
Map.Entry<T, Entry<Y>> last;
181+
Iterator<Map.Entry<T, Entry<Y>>> cacheIterator;
167182
while (currentSize > size) {
168183
cacheIterator = cache.entrySet().iterator();
169184
last = cacheIterator.next();
170-
final Y toRemove = last.getValue();
171-
currentSize -= getSize(toRemove);
185+
final Entry<Y> toRemove = last.getValue();
186+
currentSize -= toRemove.size;
172187
final T key = last.getKey();
173188
cacheIterator.remove();
174-
onItemEvicted(key, toRemove);
189+
onItemEvicted(key, toRemove.value);
175190
}
176191
}
177192

178193
private void evict() {
179194
trimToSize(maxSize);
180195
}
196+
197+
@Synthetic
198+
static final class Entry<Y> {
199+
final Y value;
200+
final int size;
201+
202+
@Synthetic
203+
Entry(Y value, int size) {
204+
this.value = value;
205+
this.size = size;
206+
}
207+
}
181208
}

library/test/src/test/java/com/bumptech/glide/load/engine/cache/LruCacheTest.java

+54-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.bumptech.glide.load.engine.cache;
22

3+
import static com.google.common.truth.Truth.assertThat;
34
import static org.junit.Assert.assertEquals;
45
import static org.junit.Assert.assertFalse;
56
import static org.junit.Assert.assertNull;
@@ -199,7 +200,7 @@ public void testCanPutSameItemMultipleTimes() {
199200
}
200201

201202
@Test
202-
public void put_withSameValueTwice_doesNotEvictItems() {
203+
public void put_withSameKeyAndValueTwice_doesNotEvictItems() {
203204
String key = getKey();
204205
Object value = new Object();
205206
cache.put(key, value);
@@ -318,6 +319,58 @@ public void testGetMaxSizeReturnsCurrentMaxSizeOfCache() {
318319
assertEquals(SIZE, cache.getMaxSize());
319320
}
320321

322+
@Test
323+
public void setSizeMultiplier_withItemWhoseSizeDecreasesAfterAdd_doesNotCrash() {
324+
Object itemWhoseSizeWillChange = new Object();
325+
when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn(SIZE / 2);
326+
cache.put(getKey(), itemWhoseSizeWillChange);
327+
cache.setSizeMultiplier(0);
328+
}
329+
330+
@Test
331+
public void getCurrentSize_afterRemovingItemWhoseSizeChanged_returnsZero() {
332+
Object itemWhoseSizeWillChange = new Object();
333+
when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn(SIZE / 2);
334+
String key = getKey();
335+
cache.put(key, itemWhoseSizeWillChange);
336+
cache.remove(key);
337+
338+
assertThat(cache.getCurrentSize()).isEqualTo(0);
339+
}
340+
341+
@Test
342+
public void clearMemory_afterRemovingItemWhoseSizeChanged_doesNotCrash() {
343+
Object itemWhoseSizeWillChange = new Object();
344+
when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn((SIZE / 2) - 1);
345+
String key = getKey();
346+
cache.put(key, itemWhoseSizeWillChange);
347+
cache.remove(key);
348+
349+
cache.clearMemory();
350+
}
351+
352+
@Test
353+
public void getCurrentSize_afterUpdatingItemWhoseSizeChanged_returnsTheNewSize() {
354+
Object itemWhoseSizeWillChange = new Object();
355+
when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn((SIZE / 2) - 1);
356+
String key = getKey();
357+
cache.put(key, itemWhoseSizeWillChange);
358+
cache.put(key, new Object());
359+
360+
assertThat(cache.getCurrentSize()).isEqualTo(1);
361+
}
362+
363+
@Test
364+
public void clearMemory_afterUpdatingItemWhoseSizeChanged_doesNotCrash() {
365+
Object itemWhoseSizeWillChange = new Object();
366+
when(listener.getSize(itemWhoseSizeWillChange)).thenReturn(SIZE - 1).thenReturn((SIZE / 2) - 1);
367+
String key = getKey();
368+
cache.put(key, itemWhoseSizeWillChange);
369+
cache.put(key, new Object());
370+
371+
cache.clearMemory();
372+
}
373+
321374
@Test
322375
public void testGetMaxSizeChangesIfMaxSizeChanges() {
323376
int multiplier = 2;

0 commit comments

Comments
 (0)