Skip to content

Commit

Permalink
Fix bug in weak value hashmap
Browse files Browse the repository at this point in the history
  • Loading branch information
unp1 committed Jan 13, 2025
1 parent cbdfbb0 commit e62a7e9
Showing 1 changed file with 50 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,25 @@
import java.util.LinkedHashMap;
import java.util.Objects;

import org.jspecify.annotations.Nullable;

/**
* A map implementation that associates keys with weakly referenced values, preserving the order of insertion.
* A map implementation that associates keys with weakly referenced values, preserving the order of
* insertion.
*
* <p>The {@code WeakValueLinkedHashMap} class stores values as weak references, allowing them to be
* garbage-collected when they are no longer strongly reachable elsewhere. When a value is garbage-collected,
* the associated key-value pair is automatically removed from the map. The map maintains the insertion order
* of entries, similar to {@link LinkedHashMap}.</p>
* <p>
* The {@code WeakValueLinkedHashMap} class stores values as weak references, allowing them to be
* garbage-collected when they are no longer strongly reachable elsewhere. When a value is
* garbage-collected,
* the associated key-value pair is automatically removed from the map. The map maintains the
* insertion order
* of entries, similar to {@link LinkedHashMap}.
* </p>
*
* <p>This class is suitable for use cases where memory efficiency is a concern and it is acceptable to
* lose entries when values are no longer in use.</p>
* <p>
* This class is suitable for use cases where memory efficiency is a concern and it is acceptable to
* lose entries when values are no longer in use.
* </p>
*
* @param <K> the type of keys maintained by this map
* @param <V> the type of values stored in the map
Expand All @@ -28,41 +37,51 @@ public class WeakValueLinkedHashMap<K, V> {
private final ReferenceQueue<V> queue = new ReferenceQueue<>();

/**
* Associates the specified key with the specified value in this map. If the map previously contained
* Associates the specified key with the specified value in this map. If the map previously
* contained
* a mapping for the key, the old value is replaced.
*
* <p>Entries with garbage-collected values are automatically removed before adding the new entry.</p>
* <p>
* Entries with garbage-collected values are automatically removed before adding the new entry.
* </p>
*
* @param key the key with which the specified value is to be associated
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
* @return the previous value associated with the key, or {@code null} if there was no mapping for the key
* @return the previous value associated with the key, or {@code null} if there was no mapping
* for the key
*/
public V put(K key, V value) {
public @Nullable V put(K key, V value) {
removeEntriesWithGCValues();
final var weakVal = new KeyWeakValuePair<>(key, value, queue);
var previous = delegate.put(key, weakVal);
return previous != null ? previous.get() : null;
}

/**
* Returns the value to which the specified key is mapped, or {@code null} if this map contains no
* Returns the value to which the specified key is mapped, or {@code null} if this map contains
* no
* mapping for the key.
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or {@code null} if no mapping exists
*/
public V get(K key) {
public @Nullable V get(K key) {
var result = delegate.get(key);
return result == null ? null : result.get();
}

/**
* <p>Returns {@code true} if this map contains a mapping for the specified key.</p>
* <p>
* Returns {@code true} if this map contains a mapping for the specified key.
* </p>
*
* <p>Entries with garbage-collected values are automatically removed before checking for the key.</p>
* <p>
* Entries with garbage-collected values are automatically removed before checking for the key.
* </p>
*
* @param key the key whose presence in this map is to be tested
* @return {@code true} if this map contains a mapping for the specified key, otherwise {@code false}
* @return {@code true} if this map contains a mapping for the specified key, otherwise
* {@code false}
*/
public boolean containsKey(K key) {
removeEntriesWithGCValues();
Expand All @@ -72,20 +91,24 @@ public boolean containsKey(K key) {
/**
* Internal method to remove entries whose values have been garbage-collected.
*
* <p>This method is called automatically during operations like {@code put} and {@code containsKey}
* to ensure the map remains up-to-date.</p>
* <p>
* This method is called automatically during operations like {@code put} and
* {@code containsKey}
* to ensure the map remains up-to-date.
* </p>
*/
private void removeEntriesWithGCValues() {
KeyWeakValuePair<K, V> gcValue;
while ((gcValue = (KeyWeakValuePair<K, V>) queue.poll()) != null) {
delegate.remove(delegate.get(gcValue.key));
delegate.remove(gcValue.key);
}
}

/**
* A weak reference that associates a key with its corresponding value.
*
* <p>This class is used internally by {@code WeakValueLinkedHashMap} to hold the values as
* <p>
* This class is used internally by {@code WeakValueLinkedHashMap} to hold the values as
* weak references and track the associated keys for removal when values are garbage-collected.
*
* @param <K> the type of key
Expand All @@ -97,9 +120,10 @@ final static class KeyWeakValuePair<K, V> extends WeakReference<V> {
/**
* Constructs a new {@code KeyWeakValuePair}.
*
* @param key the key associated with the value
* @param key the key associated with the value
* @param value the value to be weakly referenced
* @param queue the reference queue to which this reference will be appended after garbage collection
* @param queue the reference queue to which this reference will be appended after garbage
* collection
*/
public KeyWeakValuePair(K key, V value, ReferenceQueue<V> queue) {
super(value, queue);
Expand All @@ -109,7 +133,8 @@ public KeyWeakValuePair(K key, V value, ReferenceQueue<V> queue) {
/**
* Compares this {@code KeyWeakValuePair} with another object for equality.
*
* <p>Two {@code KeyWeakValuePair} objects are considered equal if their keys are equal
* <p>
* Two {@code KeyWeakValuePair} objects are considered equal if their keys are equal
* and their values are equal or both {@code null}.
*
* @param other the object to compare with
Expand All @@ -119,8 +144,7 @@ public KeyWeakValuePair(K key, V value, ReferenceQueue<V> queue) {
public boolean equals(Object other) {
if (this == other)
return true;
if (other == null ||
!(other instanceof KeyWeakValuePair weakVal)) {
if (!(other instanceof KeyWeakValuePair<?, ?> weakVal)) {
return false;
}
return Objects.equals(get(), weakVal.get());
Expand Down

0 comments on commit e62a7e9

Please sign in to comment.