为什么我的缓存机制不能按预期工作?

3

这个问题让我十分困惑。我有一个管理已下载位图图像缓存的静态类。这个类可能被多个线程同时访问。以下是实现代码:

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();
    }
}

我正在尝试实现一个滚动缓存,以便当达到最大缓存大小时(这是一个常见的情况),从缓存中删除最旧的位图。运行我的应用程序会产生以下输出:

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

代码在缓存大小达到30时开始正确记录“最大缓存大小已达到”,并在几次执行后保持不变。但是,之后它奇怪地增加到35。此时它会在数百个输出中保持不变。我无法让其增加到35以上。
我的实现有什么问题?鉴于addBitmapToCache方法是同步的,我对缓存大小如何超过设置的最大值感到困惑。

在Java中,当你执行.remove(0)时,它会自动将其余的项目向下移动吗? - Brandon Moore
根据Javadoc文档,它会... - assylias
也许在从地图中删除项目后再次记录大小以确保该项已被删除? - assylias
@assylias,干得好。每当缓存增加超过30个时,bitmapCache.remove(oldestUrl)未能从缓存中删除该项。您有任何想法是怎么发生的吗? - Chris Knight
我认为 Graham 关于重复的观点很好。看起来似乎没有并发问题。 - assylias
2个回答

2

存在几个问题。

首先,cachedBitmapUrlsOrder是一个List。如果同一位图被请求多次会发生什么?你会在列表中得到一堆重复的URL。所以第一次达到限制时,你从列表和映射中删除一个URL。但是相同的URL仍然在列表中,没有匹配的映射条目。因此,后续尝试从列表中删除相同的URL将不会从映射中删除任何内容,而映射将增长。

您可以通过在addBitmapToCache()开始时检查bitmapCache.containsKey(url)来避免这种情况,看看URL是否已经缓存。如果是,则不需要修改映射;只需确保将此条目记为最近的即可。

或者,将列表更改为唯一值的集合;可能是URL到时间戳的映射或集合。(我让你决定。)

另外,get()size()方法也应该同步。

get()应该更新检索到的条目的时间戳,使其成为最近的。


1
同意它正在增长的原因,但是在添加到缓存方法的开头添加一个检查map.containsKey(url)的行不是最简单的方法吗? - cotton.m
@cotton.m 是的,我已经在我的回答中添加了那个。谢谢。 - Graham Borland

2
除了Graham的回答之外,如果调用了clearCachecachedBitmapUrlsOrder不会被清除。因此,当您再次达到限制时,您将不会在这里删除任何内容:
String oldestUrl = cachedBitmapUrlsOrder.remove(0);
bitmapCache.remove(oldestUrl); // bitmapCache does not have such key

又是一个伟大的捕捉。显然我需要回到开发者101课程。 - Chris Knight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接