Problem
I have a search on a very simple spring-powered website and I want to display 10 recent inputs:
import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
@Component
public class RecentPhrases implements DisposableBean {
static final int SIZE = 10;
private final ExecutorService offeringService = Executors.newFixedThreadPool(1);
private final ConcurrentMap<String, Long> phrases = new ConcurrentHashMap<>();
@Override
public void destroy() {
offeringService.shutdownNow();
}
public void offer(final String phrase) {
offeringService.execute(() -> {
phrases.put(phrase, System.currentTimeMillis());
while (phrases.size() > SIZE) {
long oldestAge = Long.MAX_VALUE;
String oldestPhrase = null;
for (Entry<String, Long> entry : phrases.entrySet()) {
if (entry.getValue() < oldestAge) {
oldestAge = entry.getValue();
oldestPhrase = entry.getKey();
}
}
phrases.remove(oldestPhrase);
}
});
}
public Collection<String> getRecent() {
return phrases.keySet();
}
}
Is there some simpler solution that will be lightweight enough to not update the underlying collection in a separate thread?
Solution
A LinkedHashSet
would be simpler:
- When the size reaches the limit, simply remove the first entry, no need to iterate
- When adding an entry, if it already exists, you’d have to reinsert it to update the order: remove and then add
- No more need to worry about the time of insert
Another advantage of this method is that the entries will be naturally ordered. In your current solution they are unordered, which could appear odd to users. This is not a big advantage though, because you could easily sort before returning the collection.
Instead of oldestAge
and oldestPhrase
, you could use oldestEntry
(initialized e.g. by phrases.entrySet().iterator().get()
) as save a line or two.
A List
(either ArrayList
or LinkedList
) would be about equally fast if not faster than the Set
as long as the number of entries is at most 10. All you need is
phrases.remove(phrase);
if (phrases.size() > SIZE) {
phrases.remove(0);
}
phrases.add(phrase);
The correct order is maintained and the speed is probably better for such small collections.
I would not use another thread as its overhead may be comparable to what you’re doing.