# eviction-frequency

`software.visionary.cache:eviction-frequency:2026.10.13` · https://codeberg.org/caching/eviction-frequency

Least-frequently-used cache eviction

<code>LeastFrequentlyUsed</code> is an <code>EvictionStrategy</code> that, when an <code>Insert</code> exceeds its capacity, removes the key used least often, and among equals the one used longest ago.

## Rationale

**Why.** Some keys are asked for all day, and a recency order forgets one after a single quiet spell.

## Key Concepts

### Frequency

**Role.** Each <code>Insert</code> and <code>Hit</code> adds one to its key's count. A <code>Miss</code> changes nothing, and an <code>Evict</code> discards the count.

**Pattern.** STRATEGY, OBSERVER

## How It Works

**Guarantees.** No public parameter takes null and no public method returns it, declared with contrax's <code>@PreCondition(IsNotNull.class)</code> and <code>@PostCondition(IsNotNull.class)</code>.

**Thread safety.** An instance is not thread-safe: events must reach <code>update</code> one at a time.

## Lane

**Purpose.** Eviction for workloads with a stable set of popular keys.

**Alternative.** <code>eviction-recency</code> when popularity shifts.

**When to avoid.** Popularity that shifts over time: a once-popular key outlasts a newly popular one.

## Usage

Room for two: a is read three times and stays, while b and then c leave

```java
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import software.visionary.cache.Cache;
import software.visionary.cache.capacity.fixed.FixedSize;
import software.visionary.cache.events.CacheEvent;
import software.visionary.cache.events.Evict;
import software.visionary.cache.eviction.frequency.LeastFrequentlyUsed;
import software.visionary.cache.policy.CachePolicy;
import software.visionary.cache.topology.read.through.ReadThroughCacheFactory;
import software.visionary.events.Observer;

Map<String, Integer> source = Map.of("a", 1, "b", 2, "c", 3, "d", 4);
Supplier<Cache<String, Integer>> caches = new ReadThroughCacheFactory<>(
        key -> Optional.ofNullable(source.get(key)),
        new CachePolicy<>(() -> new FixedSize<>(2), LeastFrequentlyUsed::new, Instant::now));
Observer<CacheEvent<String, ?>> printEvictions = event -> {
    if (event instanceof Evict<String, ?> evicted) { System.out.println("evicted " + evicted.key()); }
};

var cache = caches.get();
cache.add(printEvictions);
Stream.of("a", "a", "a", "b", "c", "d").forEach(cache::apply);

```

Output

```text
evicted b
evicted c
```

a and b are each used twice and b's last use is older, so b leaves

```java
var tied = caches.get();
tied.add(printEvictions);
Stream.of("a", "b", "b", "a", "c").forEach(tied::apply);

```

Output

```text
evicted b
```

## Dependencies

- `software.visionary:cache:2026.10.13` at Compile
- `software.visionary.contrax:processor:2026.10.13` at Processor
- `software.visionary:junit-closure:2026.09.17` at Testing

