【问题标题】:Implementing Cache with ArrayDeque使用 ArrayDeque 实现缓存
【发布时间】:2012-10-25 10:07:34
【问题描述】:

简介

我使用ArrayDeque 并遵循Generics 解决方案实现了一个带有LRU 策略的简单缓存:

public class Cache<T> extends ArrayDeque<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    private int MAX_SIZE;

    public Cache(int maxSize) {
        MAX_SIZE = maxSize;
    }

    public void store(T e) {
        if (super.size() >= MAX_SIZE) {                 
            this.pollLast();
        }
        this.addFirst(e);       
    }

    public T fetch(T e) {
        Iterator<T> it = this.iterator();
        while (it.hasNext()) {
            T current = it.next();
            if (current.equals(e)) {
                this.remove(current);
                this.addFirst(current);
                return current;
            }
        }
        return null;
    }

}

问题

当我实例化类并推送一个元素时,

Cache<CachedClass> cache = new Cache<CachedClass>(10);
cache.store(new CachedClass());

此时队列中不包含任何内容。

为什么会这样?


观察

顺便说一句,CachedClass 覆盖了方法 .equals()


测试

 public class CacheTest {

    @Test
    public void testStore() {
        Cache<Integer> cache = new Cache<Integer>(3);

        cache.store(1);
        assertTrue(cache.contains(1));

        cache.store(2);
        cache.store(3);
        cache.store(4);

        assertEquals(cache.size(), 3);      
    }

    @Test
    public void testFetch() {
        Cache<Context> cache = new Cache<Context>(2);

        Context c1 = new Context(1);
        Context c2 = new Context(2);

        cache.store(c1);
        cache.store(c2);                

        assertEquals((Context) cache.peekFirst(), (new Context(2)));

        Context c = cache.fetch(c1);

        assertTrue(c == c1);        
        assertEquals(cache.size(), 2);
        assertEquals((Context) cache.peekFirst(), (new Context(1)));

    }

 }

EDIT它成功通过了两个测试。

它通过了第一个测试。它无法在

上抛出AssertException
assertTrue(cache.peekFirst() == 1);

第二次测试,

【问题讨论】:

  • 在构造函数中设置静态变量不是好的做法。但这不是您的问题的一部分。
  • 您如何确定队列中没有任何内容?
  • CachedClass 是否覆盖等于?

标签: java generics


【解决方案1】:

LinkedHashMap 的 Javadoc 说

“这种地图非常适合构建 LRU 缓存。”

您确实需要一个很好的理由来忽略这一点。我的猜测是,puts 的实现和使用 Map 的 get 实现之间的性能将无法区分——但是,嘿,你为什么不运行自己的基准测试。

最后,您的实现(以及 LinkedHashMap 提供的实现)不是线程安全的。如果这对您来说是个问题,同步逻辑将增加性能开销。

【讨论】:

【解决方案2】:

我在 main 方法中这样做:

    Cache<Integer> cache = new Cache<Integer>(10);
    cache.store(new Integer(0)); 
    System.out.println(cache.size()); // prints 1
    Integer foo = cache.fetch(new Integer(0));
    System.out.println(foo == null); // prints false

它会打印 1,因此您的 Cache 中有 1 个元素。工作正常。

用于获取您的 CachedClass 必须覆盖 equals()。您的代码无需更改即可按预期完美运行。

【讨论】:

    【解决方案3】:

    我使用 LinkedHashMap 作为 LRU 缓存。

    public static <K,V> Map<K,V> lruCache(final int maxSize) {
        return new LinkedHashMap<K,V>(maxSize*4/3, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
                return size() > maxSize;
            }
        };
    }
    

    一个重要的区别是键与值相关联。如果您将缓存作为一个集合实现,您所能做的就是确认您已经拥有的对象是否在缓存中,恕我直言,这不是很有用。

    测试

    @Test
    public void testStore() {
        Map<Integer, String> cache = lruCache(3);
        cache.put(1, "one");
        assertEquals("one", cache.get(1));
    
        cache.put(2, "two");
        cache.put(3, "three");
        cache.put(4, "four");
    
        assertEquals(cache.size(), 3);
        assertEquals(null, cache.get(1));
    }
    
    @Test
    public void testFetch() {
        Map<Integer, String> cache = lruCache(3);
    
        cache.put(1, "one");
        cache.put(2, "two");
    
        assertEquals((Integer) 1, cache.keySet().iterator().next());
    
        String s = cache.get(1);
    
        assertEquals("one", s);
        assertEquals(cache.size(), 2);
        assertEquals((Integer) 2, cache.keySet().iterator().next());
    }
    

    【讨论】:

    • 我认为缓存的重点是速度。我使用 ArrayDeque 是因为它是最快的集合 (stackoverflow.com/questions/6129805/…)。我的想法是,在获取时,通过迭代元素,您可以获得 LRU 的优势。我明白你的意思,但额外的功能被添加为 ChachedClass 中的字段。
    • 使用 LHM 查找是 O(1),而使用 ArrayDeque 查找是 O(N),所以我看不出它有多快。
    • 是的,你是对的,但是存储/获取呢? ArrayDeque 是 O(1) 用于删除/添加。对于查找,您从头开始,希望 LRU 能够正确处理给定的问题。
    • ArrayDeque 是 O(1) 用于删除第一个元素,否则它是 O(N)。而不是希望,我会使用一个集合不仅仅是 O(1),而是在一个操作中执行 get+remove+add,如果你想让它成为线程安全的,这将简化事情。 ;)
    猜你喜欢
    • 1970-01-01
    • 2015-06-17
    • 1970-01-01
    • 2015-10-01
    • 2023-04-01
    • 2015-02-13
    • 1970-01-01
    • 1970-01-01
    • 2012-05-08
    相关资源
    最近更新 更多