【问题标题】:LRUCache entry reordering when using get使用 get 时 LRUCache 条目重新排序
【发布时间】:2017-07-13 07:26:50
【问题描述】:

我查看了 LRUCache 的官方 Android 文档,其中说:每次访问一个值时,它都会移动到队列的头部。当一个值被添加到一个完整的缓存中时,该队列末尾的值被逐出并可能成为垃圾回收的条件。 我想这是由缓存使用的linkedhashmap维护的双向链表。为了检查这种行为,我检查了 LruCache 的源代码,并检查了 get(K key) 方法。它进一步调用 map 的 get 方法,该方法从底层 hashmap 中获取值并调用 recordAccess 方法。

public V get(Object key) {
    LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
    if (e == null)
        return null;
    e.recordAccess(this);
    return e.value;
}

recordAccess 方法反过来将访问的条目移动到列表的末尾,以防 accessOrder 设置为 true(对于我的问题,我们假设它是),否则它什么也不做。

/**
     * This method is invoked by the superclass whenever the value
     * of a pre-existing entry is read by Map.get or modified by Map.set.
     * If the enclosing Map is access-ordered, it moves the entry
     * to the end of the list; otherwise, it does nothing.
     */
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
    }

这听起来与上面所说的元素被移动到队列头部的语句相矛盾。相反,它被移动到列表的最后一个元素(使用 head.before)。当然,我在这里遗漏了一些东西,有什么帮助吗?

【问题讨论】:

  • 我不知道你在检查什么来源,我只能看到this
  • 我正在检查相同的来源,并且实际的重新排序发生在 LinkedHashMap 类中(因为这是维护列表的地方),所以你需要进入 map.get() 方法。
  • 好的,所以他们指的是一些虚拟的"queue",而不是LinkedHashMap的实现细节(映射反转)
  • Uhm...add**Before**(lm.**head**er)...该元素将成为新的头部...头部是列表中的第一个...“而是移动到列表的最后一个元素(使用head.before)。”是您的错误陈述。
  • @D.Kovács 请从源代码检查实现。私有瞬态 LinkedHashMapEntry 标头;是 addBefore 方法中未修改的标头。只有 header 的 before 字段被修改为指向新的最后一个元素。新元素基本上插入在最后一个元素和标题之间,因为它是一个循环列表。请为您的陈述提供任何来源,因为我在代码中找不到相同的来源。

标签: java android linkedhashmap android-lru-cache


【解决方案1】:

您没有遗漏任何东西,只是您正在阅读LinkedHashMapLruCache 文档。 LinkedHashMap 有自己的文档,尤其是关于它的accessOrder。 (Java docs 也一样)。

[...when accessOrder=true...] 迭代顺序是其条目最后一次访问的顺序,从最近最少访问到最近访问(访问顺序)

所以LinkedHashMap 将最近使用的条目放在最后,并记录在案。

实际上LruCache 描述了这种缓存在理论上是如何工作的,但LinkedHashMap 展示了如何在不添加单独的向后移动迭代器的情况下实现它:通过将最近的元素放在末尾,trimming 可以使用已经可用的 (向前移动的)迭代器来有效地访问(和删除)旧元素。

虽然此时此地我不知道removeEldestEntry 出了什么问题。也许它在过去不存在。

【讨论】:

    【解决方案2】:

    来自LinkedHashMap的javadoc:

    如果使用三参数构造函数,并且 accessOrder 指定为 true,则迭代将按照访问条目的顺序进行。访问顺序受 putgetputAll 操作影响,但不受集合视图上的操作影响。

    Exactly the caseLruCache 有。

    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
    

    让我们看看recordAccess() 做了什么:

        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) { // true, because `LruCache` instantiated this 
                                  // map with `accessOrder = true`
                lm.modCount++;
                remove(); // remove this `LinkedHashMapEntry` from the map
                addBefore(lm.header); // adds this entry before the current header of
                                      // the map, thus this entry becomes the header
            }
        }
    

    而是移动到列表的最后一个元素(使用 head.before)。

    我看不出你的陈述是如何有效的。

    【讨论】:

    • 要使entry成为header,必须在addBefore方法中修改lm.header。我没有看到任何对 lm.header 字段的分配会导致标题更改。
    • addBefore() implementation 中,您可以看到引用是如何更改的:输入条目被添加为当前标题项的before 项,输入条目的after 指向前一个标题项。所以,只是引用被扔了。
    • 同意将该项添加为 header.before 并且它的 after 指针指向 header。但标题本身没有更新。这是一种在标题之前添加内容的方法。但我没有看到 lm.header 的任何字段(传递给方法)被修改,这构成了我的问题的基础。
    • 据我了解,您希望看到旧标题项的实际值如何更改。这不是链表的工作方式。想象一下对象(foo1 - foo2 - foo3):这里是foo1.after = foo2foo2.before = foo1:每个项目引用它之前和之后的项目。现在foo4.addBefore(foo1) 将执行以下操作:foo4.after = foo1foo4.before = foo1.before(让我们将此项目命名为x)、x.after = foo4foo1.before = foo4。这将导致以下序列:foo4 - foo1 - foo2 - foo3。我们只是操纵了引用,没有改变实际的对象数据。
    • But I don't see any fields of lm.header being modified foo1.before = foo4修改foo1的字段,指向当前添加的header项。
    猜你喜欢
    • 2011-09-11
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    • 2019-06-11
    • 2016-11-13
    • 1970-01-01
    • 2019-07-02
    • 1970-01-01
    相关资源
    最近更新 更多