1313
1414import java .util .Arrays ;
1515import java .util .Objects ;
16+ import java .util .concurrent .ConcurrentHashMap ;
1617
1718/**
1819 * Fixed-size concurrent cache with approximate FIFO eviction per hash set. The cache never grows beyond its slot
2122public class ConcurrentCache <K , V > {
2223
2324 private static final int WAYS = 4 ;
25+ private static final float LOAD_FACTOR = 0.75f ;
2426
2527 private final Entry <K , V >[] entries ;
2628 private final int [] nextVictim ;
2729 private final int setMask ;
2830 private final int setShift ;
2931
32+ protected final ConcurrentHashMap <K , V > cache ;
33+
3034 private volatile long generation = 1 ;
3135
3236 @ SuppressWarnings ("unchecked" )
3337 public ConcurrentCache (int capacity ) {
38+ cache = new ConcurrentHashMap <>((int ) (Math .max (1 , capacity ) / LOAD_FACTOR ), LOAD_FACTOR );
3439 if (capacity <= 0 ) {
3540 entries = (Entry <K , V >[]) new Entry [0 ];
3641 nextVictim = new int [0 ];
@@ -50,7 +55,7 @@ public ConcurrentCache(int capacity) {
5055 public V get (Object key ) {
5156 Objects .requireNonNull (key );
5257 if (entries .length == 0 ) {
53- return null ;
58+ return cache . get ( key ) ;
5459 }
5560
5661 int hash = spread (key .hashCode ());
@@ -65,14 +70,17 @@ && sameKey(key, entry.key)) {
6570 return entry .value ;
6671 }
6772 }
68- return null ;
73+ return cache . get ( key ) ;
6974 }
7075
7176 public V put (K key , V value ) {
7277 Objects .requireNonNull (key );
7378 Objects .requireNonNull (value );
79+ cleanUp ();
80+
81+ V previous = cache .put (key , value );
7482 if (entries .length == 0 ) {
75- return null ;
83+ return previous ;
7684 }
7785
7886 int hash = spread (key .hashCode ());
@@ -96,13 +104,13 @@ public V put(K key, V value) {
96104 if (entry .value != value ) {
97105 entries [slot ] = new Entry <>(key , value , hash , currentGeneration );
98106 }
99- return entry . value ;
107+ return previous ;
100108 }
101109 }
102110
103111 if (emptySlot >= 0 ) {
104112 entries [emptySlot ] = new Entry <>(key , value , hash , currentGeneration );
105- return null ;
113+ return previous ;
106114 }
107115
108116 int firstVictim = nextVictim [setIndex ] & (WAYS - 1 );
@@ -112,17 +120,21 @@ public V put(K key, V value) {
112120 Entry <K , V > victim = entries [slot ];
113121 if (victim == null || victim .generation != currentGeneration || onEntryRemoval (victim .key )) {
114122 entries [slot ] = new Entry <>(key , value , hash , currentGeneration );
123+ if (victim != null && victim .generation == currentGeneration && !sameKey (victim .key , key )) {
124+ cache .remove (victim .key , victim .value );
125+ }
115126 nextVictim [setIndex ] = (victimOffset + 1 ) & (WAYS - 1 );
116- return null ;
127+ return previous ;
117128 }
118129 }
119130
120- return null ;
131+ return previous ;
121132 }
122133
123134 public void clear () {
124135 long nextGeneration = generation + 1 ;
125136 generation = nextGeneration == 0 ? 1 : nextGeneration ;
137+ cache .clear ();
126138 Arrays .fill (entries , null );
127139 Arrays .fill (nextVictim , 0 );
128140 }
@@ -136,6 +148,10 @@ protected boolean onEntryRemoval(K key) {
136148 return true ;
137149 }
138150
151+ protected void cleanUp () {
152+ // Legacy hook. Eviction happens during put.
153+ }
154+
139155 private int setBase (int hash ) {
140156 return (hash & setMask ) * WAYS ;
141157 }
0 commit comments