diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/MapUtil.java index b67db11f6..fac15387e 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/MapUtil.java @@ -1056,6 +1056,21 @@ public class MapUtil extends MapGetUtil { return removeIf(map, entry -> null == entry.getValue()); } + /** + * 去除Map中值为指定值的键值对
+ * 注意:此方法在传入的Map上直接修改。 + * + * @param key的类型 + * @param value的类型 + * @param map Map + * @param value 给定值 + * @return map + * @since 6.0.0 + */ + public static Map removeByValue(final Map map, final V value) { + return removeIf(map, entry -> ObjUtil.equals(value, entry.getValue())); + } + /** * 去除Map中值为{@code null}的键值对
* 注意:此方法在传入的Map上直接修改。 diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/ReferenceConcurrentMap.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/ReferenceConcurrentMap.java new file mode 100644 index 000000000..6389fef0c --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/ReferenceConcurrentMap.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2023-2024. looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.map.reference; + +import org.dromara.hutool.core.map.MapUtil; +import org.dromara.hutool.core.util.ReferenceUtil; + +import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.*; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * 线程安全的ReferenceMap实现 + * + * @param 键类型 + * @param 值类型 + * @author looly + */ +public abstract class ReferenceConcurrentMap implements ConcurrentMap, Iterable>, Serializable { + private static final long serialVersionUID = 1L; + + final ConcurrentMap, Reference> raw; + private final ReferenceQueue lastKeyQueue; + private final ReferenceQueue lastValueQueue; + /** + * 回收监听 + */ + private BiConsumer, Reference> purgeListener; + + // region 构造 + + /** + * 构造 + * + * @param raw {@link ConcurrentMap}实现 + */ + public ReferenceConcurrentMap(final ConcurrentMap, Reference> raw) { + this.raw = raw; + lastKeyQueue = new ReferenceQueue<>(); + lastValueQueue = new ReferenceQueue<>(); + } + // endregion + + /** + * 设置对象回收清除监听 + * + * @param purgeListener 监听函数 + */ + public void setPurgeListener(final BiConsumer, Reference> purgeListener) { + this.purgeListener = purgeListener; + } + + @Override + public int size() { + this.purgeStale(); + return this.raw.size(); + } + + @Override + public boolean isEmpty() { + this.purgeStale(); + return this.raw.isEmpty(); + } + + @Override + public V get(final Object key) { + this.purgeStale(); + return unwrap(this.raw.get(wrapKey(key))); + } + + @Override + public boolean containsKey(final Object key) { + this.purgeStale(); + return this.raw.containsKey(wrapKey(key)); + } + + @Override + public boolean containsValue(final Object value) { + this.purgeStale(); + return this.raw.containsValue(wrapValue(value)); + } + + @Override + public V put(final K key, final V value) { + this.purgeStale(); + final Reference vReference = this.raw.put(wrapKey(key), wrapValue(value)); + return unwrap(vReference); + } + + @Override + public V putIfAbsent(final K key, final V value) { + this.purgeStale(); + final Reference vReference = this.raw.putIfAbsent(wrapKey(key), wrapValue(value)); + return unwrap(vReference); + } + + @Override + public void putAll(final Map m) { + m.forEach(this::put); + } + + @Override + public V replace(final K key, final V value) { + this.purgeStale(); + final Reference vReference = this.raw.replace(wrapKey(key), wrapValue(value)); + return unwrap(vReference); + } + + @Override + public boolean replace(final K key, final V oldValue, final V newValue) { + this.purgeStale(); + return this.raw.replace(wrapKey(key), wrapValue(oldValue), wrapValue(newValue)); + } + + @Override + public void replaceAll(final BiFunction function) { + this.purgeStale(); + this.raw.replaceAll((rKey, rValue) -> wrapValue(function.apply(unwrap(rKey), unwrap(rValue)))); + } + + @Override + public V computeIfAbsent(final K key, final Function mappingFunction) { + this.purgeStale(); + final Reference vReference = this.raw.computeIfAbsent(wrapKey(key), + kReference -> wrapValue(mappingFunction.apply(unwrap(kReference)))); + return unwrap(vReference); + } + + @Override + public V computeIfPresent(final K key, final BiFunction remappingFunction) { + this.purgeStale(); + final Reference vReference = this.raw.computeIfPresent(wrapKey(key), + (kReference, vReference1) -> wrapValue(remappingFunction.apply(unwrap(kReference), unwrap(vReference1)))); + return unwrap(vReference); + } + + @Override + public V remove(final Object key) { + this.purgeStale(); + return unwrap(this.raw.remove(wrapKey(key))); + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(final Object key, final Object value) { + this.purgeStale(); + return this.raw.remove(wrapKey((K) key, null), value); + } + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public void clear() { + this.raw.clear(); + while (lastKeyQueue.poll() != null) ; + while (lastValueQueue.poll() != null) ; + } + + @Override + public Set keySet() { + this.purgeStale(); + final Set> referenceSet = this.raw.keySet(); + return new AbstractSet() { + @Override + public Iterator iterator() { + final Iterator> referenceIter = referenceSet.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return referenceIter.hasNext(); + } + + @Override + public K next() { + return unwrap(referenceIter.next()); + } + }; + } + + @Override + public int size() { + return referenceSet.size(); + } + }; + } + + @Override + public Collection values() { + this.purgeStale(); + final Collection> referenceValues = this.raw.values(); + return new AbstractCollection() { + @Override + public Iterator iterator() { + final Iterator> referenceIter = referenceValues.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return referenceIter.hasNext(); + } + + @Override + public V next() { + return unwrap(referenceIter.next()); + } + }; + } + + @Override + public int size() { + return referenceValues.size(); + } + }; + } + + @Override + public Set> entrySet() { + this.purgeStale(); + final Set, Reference>> referenceEntrySet = this.raw.entrySet(); + return new AbstractSet>() { + @Override + public Iterator> iterator() { + final Iterator, Reference>> referenceIter = referenceEntrySet.iterator(); + return new Iterator>() { + @Override + public boolean hasNext() { + return referenceIter.hasNext(); + } + + @Override + public Entry next() { + final Entry, Reference> next = referenceIter.next(); + return new Entry() { + @Override + public K getKey() { + return unwrap(next.getKey()); + } + + @Override + public V getValue() { + return unwrap(next.getValue()); + } + + @Override + public V setValue(final V value) { + return unwrap(next.setValue(wrapValue(value))); + } + }; + } + }; + } + + @Override + public int size() { + return referenceEntrySet.size(); + } + }; + } + + @Override + public void forEach(final BiConsumer action) { + this.purgeStale(); + this.raw.forEach((key, rValue) -> action.accept(key.get(), unwrap(rValue))); + } + + @Override + public Iterator> iterator() { + return entrySet().iterator(); + } + + @Override + public V compute(final K key, final BiFunction remappingFunction) { + this.purgeStale(); + return unwrap(this.raw.compute(wrapKey(key), + (kReference, vReference) -> wrapValue(remappingFunction.apply(unwrap(kReference), unwrap(vReference))))); + } + + @Override + public V merge(final K key, final V value, final BiFunction remappingFunction) { + this.purgeStale(); + return unwrap(this.raw.merge(wrapKey(key), wrapValue(value), + (vReference, vReference2) -> wrapValue(remappingFunction.apply(unwrap(vReference), unwrap(vReference2))))); + } + + /** + * 清除被回收的键和值 + */ + @SuppressWarnings("unchecked") + private void purgeStale() { + Reference key; + Reference value; + + // 清除无效key对应键值对 + while ((key = this.lastKeyQueue.poll()) != null) { + value = this.raw.remove(key); + if (null != purgeListener) { + purgeListener.accept(key, value); + } + } + + // 清除无效value对应的键值对 + while ((value = this.lastValueQueue.poll()) != null) { + MapUtil.removeByValue(this.raw, (Reference) value); + if (null != purgeListener) { + purgeListener.accept(null, value); + } + } + } + + /** + * 根据Reference类型构建key对应的{@link Reference} + * + * @param key 键 + * @param queue {@link ReferenceQueue} + * @return {@link Reference} + */ + abstract Reference wrapKey(final K key, final ReferenceQueue queue); + + /** + * 根据Reference类型构建value对应的{@link Reference} + * + * @param value 值 + * @param queue {@link ReferenceQueue} + * @return {@link Reference} + */ + abstract Reference wrapValue(final V value, final ReferenceQueue queue); + + /** + * 根据Reference类型构建key对应的{@link Reference} + * + * @param key 键 + * @return {@link Reference} + */ + @SuppressWarnings("unchecked") + private Reference wrapKey(final Object key) { + return wrapKey((K) key, this.lastKeyQueue); + } + + /** + * 根据Reference类型构建value对应的{@link Reference} + * + * @param value 键 + * @return {@link Reference} + */ + @SuppressWarnings("unchecked") + private Reference wrapValue(final Object value) { + return wrapValue((V) value, this.lastValueQueue); + } + + /** + * 去包装对象 + * + * @param 对象类型 + * @param obj 对象 + * @return 值 + */ + private static T unwrap(final Reference obj) { + return ReferenceUtil.get(obj); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/ReferenceKeyConcurrentMap.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/ReferenceKeyConcurrentMap.java index 2bf528080..24ac7f24b 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/ReferenceKeyConcurrentMap.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/ReferenceKeyConcurrentMap.java @@ -13,20 +13,12 @@ package org.dromara.hutool.core.map.reference; import org.dromara.hutool.core.collection.CollUtil; -import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.ReferenceUtil; import java.io.Serializable; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; -import java.lang.ref.SoftReference; -import java.lang.ref.WeakReference; -import java.util.AbstractMap; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentMap; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -244,80 +236,10 @@ public class ReferenceKeyConcurrentMap implements ConcurrentMap, Ite private Reference ofKey(final K key, final ReferenceQueue queue) { switch (keyType) { case WEAK: - return new WeakKey<>(key, queue); + return new WeakObj<>(key, queue); case SOFT: - return new SoftKey<>(key, queue); + return new SoftObj<>(key, queue); } throw new IllegalArgumentException("Unsupported key type: " + keyType); } - - /** - * 弱键 - * - * @param 键类型 - */ - private static class WeakKey extends WeakReference { - private final int hashCode; - - /** - * 构造 - * - * @param key 原始Key,不能为{@code null} - * @param queue {@link ReferenceQueue} - */ - WeakKey(final K key, final ReferenceQueue queue) { - super(key, queue); - hashCode = key.hashCode(); - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(final Object other) { - if (other == this) { - return true; - } else if (other instanceof WeakKey) { - return ObjUtil.equals(((WeakKey) other).get(), get()); - } - return false; - } - } - - /** - * 弱键 - * - * @param 键类型 - */ - private static class SoftKey extends SoftReference { - private final int hashCode; - - /** - * 构造 - * - * @param key 原始Key,不能为{@code null} - * @param queue {@link ReferenceQueue} - */ - SoftKey(final K key, final ReferenceQueue queue) { - super(key, queue); - hashCode = key.hashCode(); - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(final Object other) { - if (other == this) { - return true; - } else if (other instanceof SoftKey) { - return ObjUtil.equals(((SoftKey) other).get(), get()); - } - return false; - } - } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/SoftObj.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/SoftObj.java new file mode 100644 index 000000000..682e39d38 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/SoftObj.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024. looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.map.reference; + +import org.dromara.hutool.core.util.ObjUtil; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; + +/** + * 弱键 + * + * @param 键类型 + */ +public class SoftObj extends SoftReference { + private final int hashCode; + + /** + * 构造 + * + * @param key 原始Key,不能为{@code null} + * @param queue {@link ReferenceQueue} + */ + public SoftObj(final T key, final ReferenceQueue queue) { + super(key, queue); + hashCode = key.hashCode(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } else if (other instanceof SoftObj) { + return ObjUtil.equals(((SoftObj) other).get(), get()); + } + return false; + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/WeakObj.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/WeakObj.java new file mode 100644 index 000000000..efd7ce801 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/reference/WeakObj.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024. looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.map.reference; + +import org.dromara.hutool.core.util.ObjUtil; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +/** + * 弱键 + * + * @param 键类型 + */ +public class WeakObj extends WeakReference { + private final int hashCode; + + /** + * 构造 + * + * @param key 原始Key,不能为{@code null} + * @param queue {@link ReferenceQueue} + */ + public WeakObj(final T key, final ReferenceQueue queue) { + super(key, queue); + hashCode = key.hashCode(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } else if (other instanceof WeakObj) { + return ObjUtil.equals(((WeakObj) other).get(), get()); + } + return false; + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/util/ReferenceUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/util/ReferenceUtil.java index a3eea8268..db56d1f82 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/util/ReferenceUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/util/ReferenceUtil.java @@ -35,8 +35,8 @@ public class ReferenceUtil { /** * 获得引用 * - * @param 被引用对象类型 - * @param type 引用类型枚举 + * @param 被引用对象类型 + * @param type 引用类型枚举 * @param referent 被引用对象 * @return {@link Reference} */ @@ -47,35 +47,50 @@ public class ReferenceUtil { /** * 获得引用 * - * @param 被引用对象类型 - * @param type 引用类型枚举 + * @param 被引用对象类型 + * @param type 引用类型枚举 * @param referent 被引用对象 - * @param queue 引用队列 + * @param queue 引用队列 * @return {@link Reference} */ public static Reference of(final ReferenceType type, final T referent, final ReferenceQueue queue) { switch (type) { - case SOFT: - return new SoftReference<>(referent, queue); - case WEAK: - return new WeakReference<>(referent, queue); - case PHANTOM: - return new PhantomReference<>(referent, queue); - default: - return null; + case SOFT: + return new SoftReference<>(referent, queue); + case WEAK: + return new WeakReference<>(referent, queue); + case PHANTOM: + return new PhantomReference<>(referent, queue); + default: + return null; } } + /** + * {@code null}全的解包获取原始对象 + * + * @param 对象类型 + * @param obj Reference对象 + * @return 原始对象 or {@code null} + * @since 6.0.0 + */ + public static T get(final Reference obj) { + return ObjUtil.apply(obj, Reference::get); + } + /** * 引用类型 * * @author looly - * */ public enum ReferenceType { - /** 软引用,在GC报告内存不足时会被GC回收 */ + /** + * 软引用,在GC报告内存不足时会被GC回收 + */ SOFT, - /** 弱引用,在GC时发现弱引用会回收其对象 */ + /** + * 弱引用,在GC时发现弱引用会回收其对象 + */ WEAK, /** * 虚引用,在GC时发现虚引用对象,会将{@link PhantomReference}插入{@link ReferenceQueue}。