만료 키가있는 Java 시간 기반 맵 / 캐시 [닫기]


253

지정된 시간 초과 후 항목을 자동으로 제거하는 Java Map 또는 유사한 표준 데이터 저장소에 대해 알고 있습니까? 이는 오래된 만료 된 항목이 자동으로 "나중 만료"되는 에이징을 의미합니다.

Maven을 통해 액세스 할 수있는 오픈 소스 라이브러리에서?

기능을 직접 구현하고 과거에 여러 번 수행 한 방법을 알고 있으므로 그 점에서 조언을 구하는 것이 아니라 좋은 참조 구현을 가리키는 포인터입니다.

WeakHashMap 과 같은 WeakReference 기반 솔루션 은 옵션이 아닙니다. 내 키는 비 인턴 문자열 일 가능성이 높으며 가비지 수집기에 의존하지 않는 구성 가능한 시간 초과가 필요하기 때문입니다.

Ehcache 는 외부 구성 파일이 필요하기 때문에 의존하고 싶지 않은 옵션입니다. 코드 전용 솔루션을 찾고 있습니다.


1
Google 컬렉션 (현재 Guava)을 확인하십시오. 항목을 자동으로 시간 초과 할 수있는 맵이 있습니다.
dty

3
어떻게 홀수 253 upvotes와 176K 경관을 감상 할 수있는 질문 있음 -이 주제에 대한 검색 엔진에서 최고 높은 순위 - 가이드 라인을 충족하지 않는 닫힌
브라이언

답변:


320

예. Google Collections 또는 Guava 는 이제 이름을 딴 MapMaker 라는 것을 가지고 있습니다.

ConcurrentMap<Key, Graph> graphs = new MapMaker()
   .concurrencyLevel(4)
   .softKeys()
   .weakValues()
   .maximumSize(10000)
   .expiration(10, TimeUnit.MINUTES)
   .makeComputingMap(
       new Function<Key, Graph>() {
         public Graph apply(Key key) {
           return createExpensiveGraph(key);
         }
       });

최신 정보:

구아바 10.0 (2011 년 9 월 28 일 출시) 기준으로, 이러한 많은 MapMaker 메소드는 새로운 CacheBuilder 를 위해 더 이상 사용되지 않습니다 .

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
    .maximumSize(10000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(
        new CacheLoader<Key, Graph>() {
          public Graph load(Key key) throws AnyException {
            return createExpensiveGraph(key);
          }
        });

5
굉장히 구아바가 답을 찾았지만 찾지 못했습니다! (+1)
Sean Patrick Floyd

12
V10에서, 당신은 대신 CacheBuilder (사용해야 guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/...을 등 맵 메이커에서 사용되지 않습니다 만료 이후)
wwadge

49
경고 ! 를 사용하면 weakKeys()키가 아닌 == 의미를 사용하여 키가 비교됩니다 equals(). 나는 알아내는 30 분 잃은 이유를 내 문자열 - 키 입력 캐시는 :) 작동하지 않는
로랑 그레 구 아르

3
@Laurent가 언급 한 weakKeys()것은 중요합니다. weakKeys()시간의 90 %가 필요하지 않습니다.
Manu Manjunath 5

3
초보자를 위해 @ShervinAsgari (자체 포함), 업데이트 된 구아바 예제를 LoadingCache 대신 Cache를 사용하는 예제로 전환 할 수 있습니까? 이 질문은 더 잘 맞을 것입니다 (LoadingCache는 만료되는 항목을 가진 맵을 초과하는 기능을 가지고 있고 작성하기가 훨씬 복잡하기 때문에) github.com/google/guava/wiki/CachesExplained#from-a-callable
Jeutnarg

29

이것은 동일한 요구 사항과 동시성에 대해 수행 한 샘플 구현입니다. 누군가에게 유용 할 수 있습니다.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 
 * @author Vivekananthan M
 *
 * @param <K>
 * @param <V>
 */
public class WeakConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {

    private static final long serialVersionUID = 1L;

    private Map<K, Long> timeMap = new ConcurrentHashMap<K, Long>();
    private long expiryInMillis = 1000;
    private static final SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss:SSS");

    public WeakConcurrentHashMap() {
        initialize();
    }

    public WeakConcurrentHashMap(long expiryInMillis) {
        this.expiryInMillis = expiryInMillis;
        initialize();
    }

    void initialize() {
        new CleanerThread().start();
    }

    @Override
    public V put(K key, V value) {
        Date date = new Date();
        timeMap.put(key, date.getTime());
        System.out.println("Inserting : " + sdf.format(date) + " : " + key + " : " + value);
        V returnVal = super.put(key, value);
        return returnVal;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        for (K key : m.keySet()) {
            put(key, m.get(key));
        }
    }

    @Override
    public V putIfAbsent(K key, V value) {
        if (!containsKey(key))
            return put(key, value);
        else
            return get(key);
    }

    class CleanerThread extends Thread {
        @Override
        public void run() {
            System.out.println("Initiating Cleaner Thread..");
            while (true) {
                cleanMap();
                try {
                    Thread.sleep(expiryInMillis / 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void cleanMap() {
            long currentTime = new Date().getTime();
            for (K key : timeMap.keySet()) {
                if (currentTime > (timeMap.get(key) + expiryInMillis)) {
                    V value = remove(key);
                    timeMap.remove(key);
                    System.out.println("Removing : " + sdf.format(new Date()) + " : " + key + " : " + value);
                }
            }
        }
    }
}


Git Repo Link (리스너 구현)

https://github.com/vivekjustthink/WeakConcurrentHashMap

건배!!


cleanMap()예상 시간의 절반을 왜 실행 합니까?
EliuX

Bcoz는 키가 만료 (제거)되어 스레드가 극단적으로 반복되는 것을 방지합니다.
Vivek

@Vivek이지만이 구현에서는 이미 만료되었지만 여전히 캐시에있는 최대 (expiryInMillis / 2) 항목 수가있을 수 있습니다. 스레드가 만료 만료 후 항목을 삭제함에 따라
rishi007bansod

19

자체 만료 해시 맵의 구현 을 시도해 볼 수 있습니다 . 이 구현에서는 스레드를 사용하여 만료 된 항목을 제거하지 않고 모든 작업에서 자동으로 정리되는 DelayQueue 를 사용 합니다 .


나는 Guava의 버전을 더 좋아하지만 그림에 완성도를 더한 +1
Sean Patrick Floyd

@ piero86 나는 expireKey (ExpiringKey <K> delayedKey) 메소드의 delayQueue.poll () 호출이 잘못되었다고 말하고 싶습니다. 나중에 cleanup ()에서 사용할 수없는 임의의 ExpiringKey를 잃을 수 있습니다.
Stefan Zobel 2016 년

1
또 다른 문제 : 다른 키로 동일한 키를 두 번 넣을 수는 없습니다. a) put (1, 1, shortLived), b) put (1, 2, longLived) 후에는 longLived의 길이에 관계없이 키 1의 맵 항목이 shortLived ms 후에 사라집니다.
Stefan Zobel

통찰력에 감사드립니다. 이 문제를 요점에 의견으로보고 해 주시겠습니까?
pcan

당신의 제안에 따라 고정. 감사.
pcan

19

Apache Commons에는 Map에 항목을 만료하도록 데코레이터가 있습니다. PassiveExpiringMap Guava의 캐시보다 간단합니다.

추신. 동기화되지 않았습니다.


1
간단하지만 항목에 액세스 한 후에 만 ​​만료 시간을 확인합니다.
Badie

당으로 자바 독 : 전체지도의 내용을 액세스하는 것을 포함 메소드를 호출하면 (즉, containsKey (Object)을, entrySet () 등)이 장식 모든 만료 된 항목을 제거하기 전에 실제로 호출을 완료합니다.
NS 뒤 투아

이 라이브러리의 최신 버전 (Apache commons commons-collections4)이 무엇인지 보려면 여기에서 mvnrepository
NS du Toit

3

ehcache와 같은 소리는 원하는대로 과잉이지만 외부 구성 파일이 필요하지 않습니다.

일반적으로 구성을 선언적 구성 파일로 이동하는 것이 좋습니다 (따라서 새 설치에 다른 만료 시간이 필요할 때 다시 컴파일 할 필요는 없지만), 전혀 필요하지는 않지만 프로그래밍 방식으로 구성 할 수 있습니다. http://www.ehcache.org/documentation/user-guide/configuration


2

Google 컬렉션 (구아바)에는 시간 제한 (만료)을 설정할 수 있는 MapMaker 가 있으며 팩토리 방법을 사용하여 선택한 인스턴스를 생성 할 때 약하거나 약한 참조를 사용할 수 있습니다.



2

간단한 것이 필요한 사람은 다음과 같습니다. 간단한 키 만료 집합입니다. 지도로 쉽게 변환 될 수 있습니다.

public class CacheSet<K> {
    public static final int TIME_OUT = 86400 * 1000;

    LinkedHashMap<K, Hit> linkedHashMap = new LinkedHashMap<K, Hit>() {
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, Hit> eldest) {
            final long time = System.currentTimeMillis();
            if( time - eldest.getValue().time > TIME_OUT) {
                Iterator<Hit> i = values().iterator();

                i.next();
                do {
                    i.remove();
                } while( i.hasNext() && time - i.next().time > TIME_OUT );
            }
            return false;
        }
    };


    public boolean putIfNotExists(K key) {
        Hit value = linkedHashMap.get(key);
        if( value != null ) {
            return false;
        }

        linkedHashMap.put(key, new Hit());
        return true;
    }

    private static class Hit {
        final long time;


        Hit() {
            this.time = System.currentTimeMillis();
        }
    }
}

2
단일 스레드 상황에는 적합하지만 동시 상황에서는 비참하게 중단됩니다.
Sean Patrick Floyd

@SeanPatrickFloyd 당신은 LinkedHashMap 자체와 같은 의미입니까?! LinkedHashMap, HashMap과 같이 "외부 적으로 동기화해야합니다."
palindrom

예, 모든 것들과 같지만 구아바의 캐시와는 달리 (허용 된 답변)
Sean Patrick Floyd

또한 System.nanoTime()System.currentTimeMillis ()는 시스템 시간에 따라 다르며 연속적이지 않을 수 있으므로 시간 차이 계산에 사용 하는 것이 좋습니다.
Ercksen

2

일반적으로 캐시는 객체를 일정 시간 동안 유지해야하며 나중에 다시 노출해야합니다. 무엇 개체를 유지하는 좋은 시간은 사용 사례에 따라 달라집니다. 나는 스레드 나 스케줄러가없는이 일을 간단하게 원했습니다. 이 방법은 저에게 효과적입니다. SoftReferences 와 달리 객체는 최소한의 시간 동안 사용할 수 있습니다. 그러나 태양이 붉은 거인으로 변할 때까지 기억 속에 남아 있지 마십시오 .

사용 예제에서 요청이 아주 최근에 수행되었는지 여부를 확인할 수있는 느린 응답 시스템을 생각해보십시오.이 경우에는 바쁜 사용자가 버튼을 여러 번 누르더라도 요청 된 조치를 두 번 수행하지 않아야합니다. 그러나 얼마 후 같은 행동이 요청되면 다시 수행되어야한다.

class Cache<T> {
    long avg, count, created, max, min;
    Map<T, Long> map = new HashMap<T, Long>();

    /**
     * @param min   minimal time [ns] to hold an object
     * @param max   maximal time [ns] to hold an object
     */
    Cache(long min, long max) {
        created = System.nanoTime();
        this.min = min;
        this.max = max;
        avg = (min + max) / 2;
    }

    boolean add(T e) {
        boolean result = map.put(e, Long.valueOf(System.nanoTime())) != null;
        onAccess();
        return result;
    }

    boolean contains(Object o) {
        boolean result = map.containsKey(o);
        onAccess();
        return result;
    }

    private void onAccess() {
        count++;
        long now = System.nanoTime();
        for (Iterator<Entry<T, Long>> it = map.entrySet().iterator(); it.hasNext();) {
            long t = it.next().getValue();
            if (now > t + min && (now > t + max || now + (now - created) / count > t + avg)) {
                it.remove();
            }
        }
    }
}

감사합니다
bigbadmouse

1
HashMap은 경쟁 조건, map.put 작업 또는 맵 크기 조정으로 인해 스레드가 안전하지 않으므로 데이터가 손상 될 수 있습니다. 여기를 참조하십시오 : mailinator.blogspot.com/2009/06/beautiful-race-condition.html
유진 Maysyuk

사실입니다. 실제로, 대부분의 Java 클래스는 스레드로부터 안전하지 않습니다. 스레드 보안이 필요한 경우 영향을받는 디자인의 모든 클래스가 요구 사항을 충족하는지 확인해야합니다.
Matthias Ronge

1

구아바 캐시는 구현이 쉬우 며 구아바 캐시를 사용하여 시간 기반 키를 만료시킬 수 있습니다. 나는 전체 게시물을 읽었으며 아래는 내 연구의 핵심을 제공합니다.

cache = CacheBuilder.newBuilder().refreshAfterWrite(2,TimeUnit.SECONDS).
              build(new CacheLoader<String, String>(){
                @Override
                public String load(String arg0) throws Exception {
                    // TODO Auto-generated method stub
                    return addcache(arg0);
                }

              }

참조 : 구아바 캐시 예


1
링크가 지금 작동하지 않으므로 업데이트하십시오
smaiakov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.