Java 对象缓存数据结构,带有;“对象过期”;

Java 对象缓存数据结构,带有;“对象过期”;,java,caching,data-structures,collections,Java,Caching,Data Structures,Collections,Java中哪种数据结构最适合实现内存中的对象缓存,其中对象有各自的过期时间 基本上,对于缓存,我可以使用提供put和get方法的Map(其中key可以是字符串),并使用“timestamp”+“object”对的有序列表来管理过期时间。因此,清理线程可以检查第一个列表条目,并在过期时删除该对象。(删除第一个元素应该在O(1)时间内)我认为你的决定是正确的。 我将使用HasMead是精确的。 < P>我会考虑使用一个现有的库,比如 EHCACHE/CODE> < < /P> 但是,如果您想自己编写

Java中哪种数据结构最适合实现内存中的对象缓存,其中对象有各自的过期时间


基本上,对于缓存,我可以使用提供put和get方法的Map(其中key可以是字符串),并使用“timestamp”+“object”对的有序列表来管理过期时间。因此,清理线程可以检查第一个列表条目,并在过期时删除该对象。(删除第一个元素应该在O(1)时间内)

我认为你的决定是正确的。
我将使用HasMead是精确的。

< P>我会考虑使用一个现有的库,比如<代码> EHCACHE/CODE> < < /P> 但是,如果您想自己编写,我不会使用后台线程,除非您需要它,因为它会增加复杂性。相反,我会让前台线程删除过期的条目


如果您只需要LRU缓存,我会使用
LinkedHashMap
。但是,如果您想要定时过期,我将使用带有
优先级队列的
HashMap
(这样您可以检查下一个过期条目是否已过期)

缓存框架现在已经相当成熟:

  • EhCache:
  • Memcached:
但是,如果您坚持要重新发明轮子,请记住要考虑内存利用率。我经常看到一个实现糟糕的缓存(
HashMap
)实际上变成了内存泄漏


请看科恩的回答:

您所描述的建筑基本上是。还有其他类似的实现,如Guava(请参阅),但我认为它不支持ExpiringMap那样的每项过期。

Guava Cachebuilder:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(10000)
       .expireAfterWrite(10, TimeUnit.MINUTES)
       .removalListener(MY_LISTENER)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });
LoadingCache-graphs=CacheBuilder.newBuilder()
.最大尺寸(10000)
.expireAfterWrite(10,时间单位:分钟)
.removalListener(我的侦听器)
.建造(
新缓存加载程序(){
公共图加载(键)引发任何异常{
返回createExpensiveGraph(键);
}
});
因为WeekHashmap不适合缓存,但是您可以始终使用
Map
,其值将符合GC for week引用的条件


最重要的是,我们总是将EhCacheMemcachedcoherence作为流行的选择。

如前面的答案所述,最好使用一种流行的内存缓存,如EhCache、Memcached等

但是,正如您希望通过自己的缓存实现它一样,它具有对象过期特性和较少的时间复杂性,我尝试这样实现它—(非常感谢任何测试评论/建议)

公共类ObjectCache{
私有易失性布尔关闭;
私有最终长对象;
私人最终长期生活;
私人最终长时间删除threadrundelay;
私有最终长objectsToRemovePerRemovalThreadRun;
私人最终原子目标;
私人最终地图库;
私有最终阻塞队列;
私有最终对象锁=新对象();
专用ScheduledExecutorService executorService;
公共对象缓存(长maxObjects、长timeToLive、长removalThreadRunDelay、长objectsToRemovePerRemovalThreadRun){
this.maxObjects=maxObjects;
this.timeToLive=timeToLive;
this.removalThreadRunDelay=removalThreadRunDelay;
this.objectsToRemovePerRemovalThreadRun=objectsToRemovePerRemovalThreadRun;
this.objectscont=new AtomicLong(0);
this.cachedDataStore=new HashMap();
this.queue=新建LinkedBlockingQueue();
}
公开作废认沽权(K键,V值){
if(key==null | | value==null){
抛出新的IllegalArgumentException(“键和值都不应为null”);
}
if(objectscont.get()+1>maxObjects){
抛出新的RuntimeException(“已达到最大对象限制。无法在缓存中存储更多对象。”);
}
//创建一个值包装器并将其添加到数据存储映射
CacheEntryWrapper entryWrapper=新的CacheEntryWrapper(键,值);
已同步(锁定){
cachedDataStore.put(key,entryWrapper);
}
//将缓存项引用添加到队列,该队列将由删除线程使用
add(entryWrapper.getCacheEntryReference());
objectsCount.incrementAndGet();
//如果尚未启动,请启动删除线程
if(executorService==null){
已同步(锁定){
if(executorService==null){
executorService=Executors.newSingleThreadScheduledExecutor();
executorService.scheduleWithFixedDelay(新的CacheEntryRemover(),0,removalThreadRunDelay,时间单位为毫秒);
}
}
}
}
公共V get(K键){
if(key==null){
抛出新的IllegalArgumentException(“键不能为空”);
}
CacheEntryWrapper入口包装器;
已同步(锁定){
entryWrapper=cachedDataStore.get(key);
if(entryWrapper!=null){
//重置上次访问时间
entryWrapper.resetLastAccessedTime();
//重置参考(以便清除弱参考)
entryWrapper.resetCacheEntryReference();
//将新引用添加到队列
add(entryWrapper.getCacheEntryReference());
}
}
return entryWrapper==null?null:entryWrapper.getValue();
}
公共无效删除(K键){
if(key==null){
抛出新的IllegalArgumentException(“键不能为空”);
}
CacheEntryWrapper入口包装器;
已同步(锁定){
entryWrapper=cachedDataStore.remove(键);
if(entryWrapper!=null){
//重置参考(以便清除弱参考)
public class ObjectCache<K, V> {

    private volatile boolean shutdown;
    private final long maxObjects;
    private final long timeToLive;
    private final long removalThreadRunDelay;
    private final long objectsToRemovePerRemovalThreadRun;
    private final AtomicLong objectsCount;
    private final Map<K, CacheEntryWrapper> cachedDataStore;
    private final BlockingQueue<CacheEntryReference> queue;
    private final Object lock = new Object();
    private ScheduledExecutorService executorService;

    public ObjectCache(long maxObjects, long timeToLive, long removalThreadRunDelay, long objectsToRemovePerRemovalThreadRun) {
        this.maxObjects = maxObjects;
        this.timeToLive = timeToLive;
        this.removalThreadRunDelay = removalThreadRunDelay;
        this.objectsToRemovePerRemovalThreadRun = objectsToRemovePerRemovalThreadRun;
        this.objectsCount = new AtomicLong(0);
        this.cachedDataStore = new HashMap<K, CacheEntryWrapper>();
        this.queue = new LinkedBlockingQueue<CacheEntryReference>();
    }

    public void put(K key, V value) {
        if (key == null || value == null) {
            throw new IllegalArgumentException("Key and Value both should be not null");
        }
        if (objectsCount.get() + 1 > maxObjects) {
            throw new RuntimeException("Max objects limit reached. Can not store more objects in cache.");
        }
        // create a value wrapper and add it to data store map
        CacheEntryWrapper entryWrapper = new CacheEntryWrapper(key, value);
        synchronized (lock) {
            cachedDataStore.put(key, entryWrapper);
        }
        // add the cache entry reference to queue which will be used by removal thread
        queue.add(entryWrapper.getCacheEntryReference());
        objectsCount.incrementAndGet();
        // start the removal thread if not started already
        if (executorService == null) {
            synchronized (lock) {
                if (executorService == null) {
                    executorService = Executors.newSingleThreadScheduledExecutor();
                    executorService.scheduleWithFixedDelay(new CacheEntryRemover(), 0, removalThreadRunDelay, TimeUnit.MILLISECONDS);
                }
            }
        }
    }

    public V get(K key) {
        if (key == null) {
            throw new IllegalArgumentException("Key can not be null");
        }
        CacheEntryWrapper entryWrapper;
        synchronized (lock) {
            entryWrapper = cachedDataStore.get(key);
            if (entryWrapper != null) {
                // reset the last access time
                entryWrapper.resetLastAccessedTime();
                // reset the reference (so the weak reference is cleared)
                entryWrapper.resetCacheEntryReference();
                // add the new reference to queue
                queue.add(entryWrapper.getCacheEntryReference());
            }
        }
        return entryWrapper == null ? null : entryWrapper.getValue();
    }

    public void remove(K key) {
        if (key == null) {
            throw new IllegalArgumentException("Key can not be null");
        }
        CacheEntryWrapper entryWrapper;
        synchronized (lock) {
            entryWrapper = cachedDataStore.remove(key);
            if (entryWrapper != null) {
                // reset the reference (so the weak reference is cleared)
                entryWrapper.resetCacheEntryReference();
            }
        }
        objectsCount.decrementAndGet();
    }

    public void shutdown() {
        shutdown = true;
        executorService.shutdown();
        queue.clear();
        cachedDataStore.clear();
    }

    public static void main(String[] args) throws Exception {
        ObjectCache<Long, Long> cache = new ObjectCache<>(1000000, 60000, 1000, 1000);
        long i = 0;
        while (i++ < 10000) {
            cache.put(i, i);
        }
        i = 0;
        while(i++ < 100) {
            Thread.sleep(1000);
            System.out.println("Data store size: " + cache.cachedDataStore.size() + ", queue size: " + cache.queue.size());
        }
        cache.shutdown();
    }

    private class CacheEntryRemover implements Runnable {
        public void run() {
            if (!shutdown) {
                try {
                    int count = 0;
                    CacheEntryReference entryReference;
                    while ((entryReference = queue.peek()) != null && count++ < objectsToRemovePerRemovalThreadRun) {
                        long currentTime = System.currentTimeMillis();
                        CacheEntryWrapper cacheEntryWrapper = entryReference.getWeakReference().get();
                        if (cacheEntryWrapper == null || !cachedDataStore.containsKey(cacheEntryWrapper.getKey())) {
                            queue.poll(100, TimeUnit.MILLISECONDS); // remove the reference object from queue as value is removed from cache
                        } else if (currentTime - cacheEntryWrapper.getLastAccessedTime().get() > timeToLive) {
                            synchronized (lock) {
                                // get the cacheEntryWrapper again just to find if put() has overridden the same key or remove() has removed it already
                                CacheEntryWrapper newCacheEntryWrapper = cachedDataStore.get(cacheEntryWrapper.getKey());
                                // poll the queue if -
                                // case 1 - value is removed from cache
                                // case 2 - value is overridden by new value
                                // case 3 - value is still in cache but it is old now
                                if (newCacheEntryWrapper == null || newCacheEntryWrapper != cacheEntryWrapper || currentTime - cacheEntryWrapper.getLastAccessedTime().get() > timeToLive) {
                                    queue.poll(100, TimeUnit.MILLISECONDS);
                                    newCacheEntryWrapper = newCacheEntryWrapper == null ? cacheEntryWrapper : newCacheEntryWrapper;
                                    if (currentTime - newCacheEntryWrapper.getLastAccessedTime().get() > timeToLive) {
                                        remove(newCacheEntryWrapper.getKey());
                                    }
                                } else {
                                    break; // try next time
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private class CacheEntryWrapper {
        private K key;
        private V value;
        private AtomicLong lastAccessedTime;
        private CacheEntryReference cacheEntryReference;

        public CacheEntryWrapper(K key, V value) {
            this.key = key;
            this.value = value;
            this.lastAccessedTime = new AtomicLong(System.currentTimeMillis());
            this.cacheEntryReference = new CacheEntryReference(this);
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public AtomicLong getLastAccessedTime() {
            return lastAccessedTime;
        }

        public CacheEntryReference getCacheEntryReference() {
            return cacheEntryReference;
        }

        public void resetLastAccessedTime() {
            lastAccessedTime.set(System.currentTimeMillis());
        }

        public void resetCacheEntryReference() {
            cacheEntryReference.clear();
            cacheEntryReference = new CacheEntryReference(this);
        }
    }

    private class CacheEntryReference {
        private WeakReference<CacheEntryWrapper> weakReference;

        public CacheEntryReference(CacheEntryWrapper entryWrapper) {
            this.weakReference = new WeakReference<CacheEntryWrapper>(entryWrapper);
        }

        public WeakReference<CacheEntryWrapper> getWeakReference() {
            return weakReference;
        }

        public void clear() {
            weakReference.clear();
        }
    }
}