I’m thoroughly stumped by this one. I have a static class which manages a cache of downloaded bitmap images. This class is accessed by multiple threads with potential for concurrent access. Here’s the implementation:
public class BitmapCache {
private static final int MAX_NUMBER_BITMAPS_TO_CACHE = 30;
private static Map<String, Bitmap> bitmapCache = new HashMap<String, Bitmap>();
private static List<String> cachedBitmapUrlsOrder = new ArrayList<String>();
private BitmapCache(){}
public static synchronized void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmapCache.size() >= MAX_NUMBER_BITMAPS_TO_CACHE) {
Log.i("MyApp", "Max cache size reached. Removing oldest bitmap. Size = " + bitmapCache.size());
String oldestUrl = cachedBitmapUrlsOrder.remove(0);
bitmapCache.remove(oldestUrl);
}
bitmapCache.put(url, bitmap);
cachedBitmapUrlsOrder.add(url);
}
public static int size() {
return bitmapCache.size();
}
public static Bitmap get(String url) {
return bitmapCache.get(url);
}
public synchronized static void clearCache() {
bitmapCache.clear();
}
}
I’m trying to implement a rolling cache so that when the max cache size is reached (a common occurence) the oldest bitmap is removed from the cache. Running my application gives the following output:
02-29 23:00:26.590: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 30
02-29 23:00:26.600: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 30
02-29 23:00:26.720: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 30
02-29 23:00:26.790: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 30
02-29 23:00:26.820: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 31
02-29 23:00:26.850: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 31
02-29 23:00:27.050: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 32
02-29 23:00:27.070: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 32
02-29 23:00:27.100: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 33
02-29 23:00:27.130: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 34
02-29 23:00:27.170: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 35
02-29 23:00:27.210: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 35
02-29 23:00:27.330: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 35
02-29 23:00:27.360: I/MyApp(10789): Max cache size reached. Removing oldest bitmap. Size = 35
The code correctly starts logging ‘Max cache size reached’ when the cache size reaches 30 and stays there for a few executions. But then it strangely begins to increase up to 35. At this point it stays there for hundreds more output. I can’t get it to increase beyond 35.
What is wrong with my implemention? Given the addBitmapToCache method is synchronized I’m baffled at how the cache size can grow beyond the maximum set.
Several problems.
Firstly,
cachedBitmapUrlsOrderis aList. What happens to it if the same bitmap is requested more than once? You get a whole bunch of duplicate URLs in the list. So the first time you hit the limit, you remove a URL from the list, and from the map. But the same URL is still in the list, with no matching map entry. So subsequent attempts to remove the same URL from the list will not remove anything from the map, and the map will grow.You can avoid this by checking, right at the start of
addBitmapToCache(), if the URL is already cached, withbitmapCache.containsKey(url). If so, you don’t need to modify the map; just make sure this entry is remembered as the most recent.Alternatively, change the list to be a collection of unique values; probably a map of URLs to timestamps, or a Set. (I’ll let you decide.)
Also, the
get()andsize()methods should be synchronized too.And
get()should update the timestamp of the retrieved entry, so that it becomes the most recent.