Skip to content

Commit 15c15ea

Browse files
Xavier DucrohetAndroid (Google) Code Review
authored andcommitted
Merge "Replace LruCache implementation for layoutlib. do not merge." into jb-dev
2 parents 00139b2 + 54a18ef commit 15c15ea

File tree

5 files changed

+428
-19
lines changed

5 files changed

+428
-19
lines changed
Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
/*
2+
* Copyright (C) 2011 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+
package android.util;
18+
19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
21+
22+
/**
23+
* BEGIN LAYOUTLIB CHANGE
24+
* This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
25+
* END LAYOUTLIB CHANGE
26+
*
27+
* A cache that holds strong references to a limited number of values. Each time
28+
* a value is accessed, it is moved to the head of a queue. When a value is
29+
* added to a full cache, the value at the end of that queue is evicted and may
30+
* become eligible for garbage collection.
31+
*
32+
* <p>If your cached values hold resources that need to be explicitly released,
33+
* override {@link #entryRemoved}.
34+
*
35+
* <p>If a cache miss should be computed on demand for the corresponding keys,
36+
* override {@link #create}. This simplifies the calling code, allowing it to
37+
* assume a value will always be returned, even when there's a cache miss.
38+
*
39+
* <p>By default, the cache size is measured in the number of entries. Override
40+
* {@link #sizeOf} to size the cache in different units. For example, this cache
41+
* is limited to 4MiB of bitmaps:
42+
* <pre> {@code
43+
* int cacheSize = 4 * 1024 * 1024; // 4MiB
44+
* LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
45+
* protected int sizeOf(String key, Bitmap value) {
46+
* return value.getByteCount();
47+
* }
48+
* }}</pre>
49+
*
50+
* <p>This class is thread-safe. Perform multiple cache operations atomically by
51+
* synchronizing on the cache: <pre> {@code
52+
* synchronized (cache) {
53+
* if (cache.get(key) == null) {
54+
* cache.put(key, value);
55+
* }
56+
* }}</pre>
57+
*
58+
* <p>This class does not allow null to be used as a key or value. A return
59+
* value of null from {@link #get}, {@link #put} or {@link #remove} is
60+
* unambiguous: the key was not in the cache.
61+
*
62+
* <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
63+
* of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
64+
* Support Package</a> for earlier releases.
65+
*/
66+
public class LruCache<K, V> {
67+
private final LinkedHashMap<K, V> map;
68+
69+
/** Size of this cache in units. Not necessarily the number of elements. */
70+
private int size;
71+
private int maxSize;
72+
73+
private int putCount;
74+
private int createCount;
75+
private int evictionCount;
76+
private int hitCount;
77+
private int missCount;
78+
79+
/**
80+
* @param maxSize for caches that do not override {@link #sizeOf}, this is
81+
* the maximum number of entries in the cache. For all other caches,
82+
* this is the maximum sum of the sizes of the entries in this cache.
83+
*/
84+
public LruCache(int maxSize) {
85+
if (maxSize <= 0) {
86+
throw new IllegalArgumentException("maxSize <= 0");
87+
}
88+
this.maxSize = maxSize;
89+
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
90+
}
91+
92+
/**
93+
* Sets the size of the cache.
94+
* @param maxSize The new maximum size.
95+
*
96+
* @hide
97+
*/
98+
public void resize(int maxSize) {
99+
if (maxSize <= 0) {
100+
throw new IllegalArgumentException("maxSize <= 0");
101+
}
102+
103+
synchronized (this) {
104+
this.maxSize = maxSize;
105+
}
106+
trimToSize(maxSize);
107+
}
108+
109+
/**
110+
* Returns the value for {@code key} if it exists in the cache or can be
111+
* created by {@code #create}. If a value was returned, it is moved to the
112+
* head of the queue. This returns null if a value is not cached and cannot
113+
* be created.
114+
*/
115+
public final V get(K key) {
116+
if (key == null) {
117+
throw new NullPointerException("key == null");
118+
}
119+
120+
V mapValue;
121+
synchronized (this) {
122+
mapValue = map.get(key);
123+
if (mapValue != null) {
124+
hitCount++;
125+
return mapValue;
126+
}
127+
missCount++;
128+
}
129+
130+
/*
131+
* Attempt to create a value. This may take a long time, and the map
132+
* may be different when create() returns. If a conflicting value was
133+
* added to the map while create() was working, we leave that value in
134+
* the map and release the created value.
135+
*/
136+
137+
V createdValue = create(key);
138+
if (createdValue == null) {
139+
return null;
140+
}
141+
142+
synchronized (this) {
143+
createCount++;
144+
mapValue = map.put(key, createdValue);
145+
146+
if (mapValue != null) {
147+
// There was a conflict so undo that last put
148+
map.put(key, mapValue);
149+
} else {
150+
size += safeSizeOf(key, createdValue);
151+
}
152+
}
153+
154+
if (mapValue != null) {
155+
entryRemoved(false, key, createdValue, mapValue);
156+
return mapValue;
157+
} else {
158+
trimToSize(maxSize);
159+
return createdValue;
160+
}
161+
}
162+
163+
/**
164+
* Caches {@code value} for {@code key}. The value is moved to the head of
165+
* the queue.
166+
*
167+
* @return the previous value mapped by {@code key}.
168+
*/
169+
public final V put(K key, V value) {
170+
if (key == null || value == null) {
171+
throw new NullPointerException("key == null || value == null");
172+
}
173+
174+
V previous;
175+
synchronized (this) {
176+
putCount++;
177+
size += safeSizeOf(key, value);
178+
previous = map.put(key, value);
179+
if (previous != null) {
180+
size -= safeSizeOf(key, previous);
181+
}
182+
}
183+
184+
if (previous != null) {
185+
entryRemoved(false, key, previous, value);
186+
}
187+
188+
trimToSize(maxSize);
189+
return previous;
190+
}
191+
192+
/**
193+
* @param maxSize the maximum size of the cache before returning. May be -1
194+
* to evict even 0-sized elements.
195+
*/
196+
private void trimToSize(int maxSize) {
197+
while (true) {
198+
K key;
199+
V value;
200+
synchronized (this) {
201+
if (size < 0 || (map.isEmpty() && size != 0)) {
202+
throw new IllegalStateException(getClass().getName()
203+
+ ".sizeOf() is reporting inconsistent results!");
204+
}
205+
206+
if (size <= maxSize) {
207+
break;
208+
}
209+
210+
// BEGIN LAYOUTLIB CHANGE
211+
// get the last item in the linked list.
212+
// This is not efficient, the goal here is to minimize the changes
213+
// compared to the platform version.
214+
Map.Entry<K, V> toEvict = null;
215+
for (Map.Entry<K, V> entry : map.entrySet()) {
216+
toEvict = entry;
217+
}
218+
// END LAYOUTLIB CHANGE
219+
220+
if (toEvict == null) {
221+
break;
222+
}
223+
224+
key = toEvict.getKey();
225+
value = toEvict.getValue();
226+
map.remove(key);
227+
size -= safeSizeOf(key, value);
228+
evictionCount++;
229+
}
230+
231+
entryRemoved(true, key, value, null);
232+
}
233+
}
234+
235+
/**
236+
* Removes the entry for {@code key} if it exists.
237+
*
238+
* @return the previous value mapped by {@code key}.
239+
*/
240+
public final V remove(K key) {
241+
if (key == null) {
242+
throw new NullPointerException("key == null");
243+
}
244+
245+
V previous;
246+
synchronized (this) {
247+
previous = map.remove(key);
248+
if (previous != null) {
249+
size -= safeSizeOf(key, previous);
250+
}
251+
}
252+
253+
if (previous != null) {
254+
entryRemoved(false, key, previous, null);
255+
}
256+
257+
return previous;
258+
}
259+
260+
/**
261+
* Called for entries that have been evicted or removed. This method is
262+
* invoked when a value is evicted to make space, removed by a call to
263+
* {@link #remove}, or replaced by a call to {@link #put}. The default
264+
* implementation does nothing.
265+
*
266+
* <p>The method is called without synchronization: other threads may
267+
* access the cache while this method is executing.
268+
*
269+
* @param evicted true if the entry is being removed to make space, false
270+
* if the removal was caused by a {@link #put} or {@link #remove}.
271+
* @param newValue the new value for {@code key}, if it exists. If non-null,
272+
* this removal was caused by a {@link #put}. Otherwise it was caused by
273+
* an eviction or a {@link #remove}.
274+
*/
275+
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
276+
277+
/**
278+
* Called after a cache miss to compute a value for the corresponding key.
279+
* Returns the computed value or null if no value can be computed. The
280+
* default implementation returns null.
281+
*
282+
* <p>The method is called without synchronization: other threads may
283+
* access the cache while this method is executing.
284+
*
285+
* <p>If a value for {@code key} exists in the cache when this method
286+
* returns, the created value will be released with {@link #entryRemoved}
287+
* and discarded. This can occur when multiple threads request the same key
288+
* at the same time (causing multiple values to be created), or when one
289+
* thread calls {@link #put} while another is creating a value for the same
290+
* key.
291+
*/
292+
protected V create(K key) {
293+
return null;
294+
}
295+
296+
private int safeSizeOf(K key, V value) {
297+
int result = sizeOf(key, value);
298+
if (result < 0) {
299+
throw new IllegalStateException("Negative size: " + key + "=" + value);
300+
}
301+
return result;
302+
}
303+
304+
/**
305+
* Returns the size of the entry for {@code key} and {@code value} in
306+
* user-defined units. The default implementation returns 1 so that size
307+
* is the number of entries and max size is the maximum number of entries.
308+
*
309+
* <p>An entry's size must not change while it is in the cache.
310+
*/
311+
protected int sizeOf(K key, V value) {
312+
return 1;
313+
}
314+
315+
/**
316+
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
317+
*/
318+
public final void evictAll() {
319+
trimToSize(-1); // -1 will evict 0-sized elements
320+
}
321+
322+
/**
323+
* For caches that do not override {@link #sizeOf}, this returns the number
324+
* of entries in the cache. For all other caches, this returns the sum of
325+
* the sizes of the entries in this cache.
326+
*/
327+
public synchronized final int size() {
328+
return size;
329+
}
330+
331+
/**
332+
* For caches that do not override {@link #sizeOf}, this returns the maximum
333+
* number of entries in the cache. For all other caches, this returns the
334+
* maximum sum of the sizes of the entries in this cache.
335+
*/
336+
public synchronized final int maxSize() {
337+
return maxSize;
338+
}
339+
340+
/**
341+
* Returns the number of times {@link #get} returned a value that was
342+
* already present in the cache.
343+
*/
344+
public synchronized final int hitCount() {
345+
return hitCount;
346+
}
347+
348+
/**
349+
* Returns the number of times {@link #get} returned null or required a new
350+
* value to be created.
351+
*/
352+
public synchronized final int missCount() {
353+
return missCount;
354+
}
355+
356+
/**
357+
* Returns the number of times {@link #create(Object)} returned a value.
358+
*/
359+
public synchronized final int createCount() {
360+
return createCount;
361+
}
362+
363+
/**
364+
* Returns the number of times {@link #put} was called.
365+
*/
366+
public synchronized final int putCount() {
367+
return putCount;
368+
}
369+
370+
/**
371+
* Returns the number of values that have been evicted.
372+
*/
373+
public synchronized final int evictionCount() {
374+
return evictionCount;
375+
}
376+
377+
/**
378+
* Returns a copy of the current contents of the cache, ordered from least
379+
* recently accessed to most recently accessed.
380+
*/
381+
public synchronized final Map<K, V> snapshot() {
382+
return new LinkedHashMap<K, V>(map);
383+
}
384+
385+
@Override public synchronized final String toString() {
386+
int accesses = hitCount + missCount;
387+
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
388+
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
389+
maxSize, hitCount, missCount, hitPercent);
390+
}
391+
}

0 commit comments

Comments
 (0)