Java
Time-Based Cache
Expiring Keys
Programming
Data Structures

Java time-based map/cache with expiring keys

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

In Java, managing data with time constraints can be very useful, especially in environments like caching where old data becomes irrelevant after a certain timeframe. An effective way to manage such data is through a time-based map or cache, where keys are set to expire after a specified duration. This concept is not natively supported in the standard Java collections framework but can be implemented using various techniques or third-party libraries. A time-based map or cache ensures efficient memory usage and data freshness, thus enhancing application performance.

Understanding Time-Based Expiration

Time-based expiration in a map or cache involves setting an expiry time for each key-value pair. When the pair expires, it is no longer valid and should be automatically removed from the collection. There are generally two types of expirations:

  1. Fixed Expiration: Each key in the map expires after a fixed duration.
  2. Variable Expiration: The expiration time may vary between different key-value pairs based on specific conditions or usage patterns.

Implementation Strategies

Implementation of expiring keys can be done in several ways:

  • Lazy Expiration: Check and remove expired entries on each access. This approach is simple but can potentially lead to using outdated data.
  • Active Expiration: Use a separate thread or scheduler to remove expired entries at regular intervals. This method ensures that the data in the map is mostly up to date, though it may impose additional resource overhead.

DIY Expiring Map

Although Java doesn’t have a built-in expiring map, one can implement it using a combination of a HashMap and a PriorityQueue. The HashMap holds the data while the PriorityQueue keeps track of the expiration times. Here’s a basic implementation outline:

java
1import java.util.*;
2
3public class ExpiringMap<K, V> {
4    private final long ttl;
5    private Map<K, V> map = new HashMap<>();
6    private PriorityQueue<ExpiryKey<K>> expiryQueue = new PriorityQueue<>();
7
8    public ExpiringMap(long ttl) {
9        this.ttl = ttl;
10    }
11
12    public void put(K key, V value) {
13        long expiryTime = System.currentTimeMillis() + ttl;
14        map.put(key, value);
15        expiryQueue.add(new ExpiryKey<>(key, expiryTime));
16    }
17
18    public V get(K key) {
19        removeExpired();
20        return map.get(key);
21    }
22
23    private void removeExpired() {
24        long currentTime = System.currentTimeMillis();
25        while (!expiryQueue.isEmpty() && expiryQueue.peek().expiryTime <= currentTime) {
26            K key = expiryQueue.poll().key;
27            map.remove(key);
28        }
29    }
30
31    private static class ExpiryKey<K> implements Comparable<ExpiryKey<K>> {
32        K key;
33        long expiryTime;
34
35        ExpiryKey(K key, long expiryTime) {
36            this.key = key;
37            this.expiryTime = expiryTime;
38        }
39
40        @Override
41        public int compareTo(ExpiryKey<K> o) {
42            return Long.compare(this.expiryTime, o.expiryTime);
43        }
44    }
45}

In this implementation, the ExpiringMap class wraps a HashMap and PriorityQueue. The PriorityQueue tracks keys by expiration time, which helps efficiently identify and remove expired keys when necessary.

Third-Party Libraries

For production-grade applications, using well-tested third-party libraries is generally recommended over building a custom solution. Libraries such as Google Guava and Caffeine provide sophisticated caching mechanisms including expirable entries. For instance, here’s how you can create a cache with Guava:

java
1import com.google.common.cache.CacheBuilder;
2import java.util.concurrent.TimeUnit;
3
4var cache = CacheBuilder.newBuilder()
5               .expireAfterWrite(10, TimeUnit.MINUTES)
6               .build();

Table: Comparison of Expiration Techniques

TechniqueComplexityMemory EfficiencyData FreshnessUse Case
Lazy ExpirationLowMediumLowMinimal resource usage
Active ExpirationHighHighHighHigh freshness requirement

Summary

Time-based maps or caches are crucial for managing data that has a temporal relevance. Whether you implement your own using core Java classes or utilize third-party libraries, proper handling of expiring keys can drastically impact the efficiency and effectiveness of your data management strategy. Choosing the right strategy depends on specific application needs, including factors like data volume, freshness requirements, and system resources.

By understanding the available techniques and tools, developers can ensure that their applications handle time-sensitive data in an efficient and effective manner.


Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.