Is there an expiring map in Java that ends with elements after a period of time with the * first * insert?

I tried to take a look at caching mechanisms like Guava Cache. Their validity is only after the last update.

I am looking for a data structure that stores keys and clears keys over time since the first insertion. I plan that the value will be some counter.

The script may be a quiet worker who does some work for the first time, but is silent for the duration of the action - even if the job is requested. If a job is requested after the expiration date, he will do the job.

Know this data structure? Thank.

0
source share
3

.

, (.. , - ), PassiveExpiringMap Apache Commons - . (TTL) ( TTL), , null. , , , TTL, , .

(, / ), Google Guava CacheBuilder . , Apache Commons, . , .

, , . , , , . , , . , , , ( ), ( - ), .

, , , TTL . ExpiringKey, ( TTL, TTL, Map decorator Map):

public class ExpiringKey<T> {

    private final T key;
    private final long expirationTimestamp;

    public ExpiringKey(T key, long ttlInMillis) {
        this.key = key;
        expirationTimestamp = System.currentTimeMillis() + ttlInMillis;
    }

    public T getKey() {
        return key;
    }

    public boolean isExpired() {
        return System.currentTimeMillis() > expirationTimestamp;
    }
}

Map<ExpiringKey<K>, V> K V. Runnable, :

public class ExpiredKeyRemover implements Runnable {

    private final Map<ExpiringKey<?>, ?> map;

    public ExpiredKeyRemover(Map<ExpiringKey<?>, ?> map) {
        this.map = map;
    }

    @Override
    public void run() {
        Iterator<ExpiringKey<?>> it = map.keySet().iterator();
        while (it.hasNext()) {
            if (it.next().isExpired()) {
                it.remove();
            }
        }
    }
}

Runnable , ScheduledExecutorService ( 5 ):

Map<ExpiringKey<K>, V> myMap = // ...

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(new ExpiredKeyRemover(myMap), 0, 5, TimeUnit.SECONDS);

, Map, myMap, . Map , ExpiredKeyRemover , , , , ( / , , , ). , , , .

, .

+2

. DuplicateActionFilterByInsertTime.

- . (filterMillis).

:

public class DuplicateActionFilterByInsertTime<E extends Runnable> {

    private static final Logger LOGGER = Logger.getLogger(DuplicateActionFilterByInsertTime.class.getName());

    private final long filterMillis;

    private final ConcurrentHashMap<E, SilenceInfoImpl> actionMap = new ConcurrentHashMap<>();

    private final ConcurrentLinkedQueue<E> actionQueue = new ConcurrentLinkedQueue<>();

    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    private final AtomicBoolean purgerRegistered = new AtomicBoolean(false);

    private final Set<Listener<E>> listeners = ConcurrentHashMap.newKeySet();

    public DuplicateActionFilterByInsertTime(int filterMillis) {
        this.filterMillis = filterMillis;
    }

    public SilenceInfo get(E e) {
        SilenceInfoImpl insertionData = actionMap.get(e);
        if (insertionData == null || insertionData.isExpired(filterMillis)) {
            return null;
        }
        return insertionData;
    }

    public boolean run(E e) {
        actionMap.computeIfPresent(e, (e1, insertionData) -> {
            int count = insertionData.incrementAndGet();
            if (count == 2) {
                notifyFilteringStarted(e1);
            }
            return insertionData;
        });
        boolean isNew = actionMap.computeIfAbsent(e, e1 -> {
            SilenceInfoImpl insertionData = new SilenceInfoImpl();
            actionQueue.add(e1);
            return insertionData;
        }).getCount() == 1;

        tryRegisterPurger();

        if (isNew) {
            e.run();
        }
        return isNew;
    }

    private void tryRegisterPurger() {
        if (actionMap.size() != 0 && purgerRegistered.compareAndSet(false, true)) {
            scheduledExecutorService.schedule(() -> {
                try {
                    for (Iterator<E> iterator = actionQueue.iterator(); iterator.hasNext(); ) {
                        E e = iterator.next();
                        SilenceInfoImpl insertionData = actionMap.get(e);
                        if (insertionData == null || insertionData.isExpired(filterMillis)) {
                            iterator.remove();
                        }
                        if (insertionData != null && insertionData.isExpired(filterMillis)) {
                            SilenceInfoImpl removed = actionMap.remove(e);
                            FilteredItem<E> filteredItem = new FilteredItem<>(e, removed);
                            notifySilenceFinished(filteredItem);
                        } else {
                            // All the elements that were left shouldn't be purged.
                            break;
                        }
                    }
                } finally {
                    purgerRegistered.set(false);
                    tryRegisterPurger();
                }
            }, filterMillis, TimeUnit.MILLISECONDS);
        }
    }

    private void notifySilenceFinished(FilteredItem<E> filteredItem) {
        new Thread(() -> listeners.forEach(l -> {
            try {
                l.onFilteringFinished(filteredItem);
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, "Purge notification failed. Continuing to next one (if exists)", e);
            }
        })).start();
    }

    private void notifyFilteringStarted(final E e) {
        new Thread(() -> listeners.forEach(l -> {
            try {
                l.onFilteringStarted(e);
            } catch (Exception e1) {
                LOGGER.log(Level.WARNING, "Silence started notification failed. Continuing to next one (if exists)", e1);
            }
        })).start();
    }

    public void addListener(Listener<E> listener) {
        listeners.add(listener);
    }

    public void removeLister(Listener<E> listener) {
        listeners.remove(listener);
    }

    public interface SilenceInfo {
        long getInsertTimeMillis();

        int getCount();
    }

    public interface Listener<E> {
        void onFilteringStarted(E e);
        void onFilteringFinished(FilteredItem<E> filteredItem);
    }

    private static class SilenceInfoImpl implements SilenceInfo {
        private final long insertTimeMillis = System.currentTimeMillis();
        private AtomicInteger count = new AtomicInteger(1);

        int incrementAndGet() {
            return count.incrementAndGet();
        }

        @Override
        public long getInsertTimeMillis() {
            return insertTimeMillis;
        }

        @Override
        public int getCount() {
            return count.get();
        }

        boolean isExpired(long expirationMillis) {
            return insertTimeMillis + expirationMillis < System.currentTimeMillis();
        }
    }

    public static class FilteredItem<E> {
        private final E item;
        private final SilenceInfo silenceInfo;

        FilteredItem(E item, SilenceInfo silenceInfo) {
            this.item = item;
            this.silenceInfo = silenceInfo;
        }

        public E getItem() {
            return item;
        }

        public SilenceInfo getSilenceInfo() {
            return silenceInfo;
        }
    }
}

: ( )

@Test
public void testSimple() throws InterruptedException {
    int filterMillis = 100;
    DuplicateActionFilterByInsertTime<Runnable> expSet = new DuplicateActionFilterByInsertTime<>(filterMillis);
    AtomicInteger purgeCount = new AtomicInteger(0);
    expSet.addListener(new DuplicateActionFilterByInsertTime.Listener<Runnable>() {
        @Override
        public void onFilteringFinished(DuplicateActionFilterByInsertTime.FilteredItem<Runnable> filteredItem) {
            purgeCount.incrementAndGet();
        }

        @Override
        public void onFilteringStarted(Runnable runnable) {
        }
    });

    Runnable key = () -> {
    };
    long beforeAddMillis = System.currentTimeMillis();
    boolean added = expSet.run(key);
    long afterAddMillis = System.currentTimeMillis();
    Assert.assertTrue(added);
    DuplicateActionFilterByInsertTime.SilenceInfo silenceInfo = expSet.get(key);
    Assertions.assertThat(silenceInfo.getInsertTimeMillis()).isBetween(beforeAddMillis, afterAddMillis);

    expSet.run(key);
    DuplicateActionFilterByInsertTime.SilenceInfo silenceInfo2 = expSet.get(key);
    Assert.assertEquals(silenceInfo.getInsertTimeMillis(), silenceInfo2.getInsertTimeMillis());

    Assert.assertFalse(silenceInfo.getInsertTimeMillis() + filterMillis < System.currentTimeMillis());
    Assert.assertEquals(silenceInfo.getCount(), 2);

    Thread.sleep(filterMillis);

    Assertions.assertThat(expSet.get(key)).isNull();

    Assert.assertNull(expSet.get(key));

    Thread.sleep(filterMillis * 2); // Give a chance to purge the items.
    Assert.assertEquals(1, purgeCount.get());

    System.out.println("Finished");
}

.

0

ExpiringMap.java. .

public static Map<String, Long> threatURLCacheMap = ExpiringMap.builder().expiration(5, TimeUnit.MINUTES).build();

, 5 . maven net.jodah.expiringmap. , https://crunchify.com/how-to-use-expiringmap-maven-java-utility-to-remove-expired-objects-from-map-automatically-complete-java-tutorial/

0

Source: https://habr.com/ru/post/1695511/


All Articles