This commit is contained in:
Looly 2023-05-06 02:19:40 +08:00
parent 7db850fe62
commit 78d4a6ee1d
15 changed files with 605 additions and 344 deletions

View File

@ -1,247 +0,0 @@
/*
* Copyright (c) 2023 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:
* http://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.lang;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.util.RandomUtil;
import java.io.Serializable;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* 权重随机算法实现<br>
* <p>
* 平时经常会遇到权重随机算法从不同权重的N个元素中随机选择一个并使得总体选择结果是按照权重分布的如广告投放负载均衡等
* </p>
* <p>
* 如有4个元素ABCD权重分别为1234随机结果中A:B:C:D的比例要为1:2:3:4<br>
* </p>
* 总体思路累加每个元素的权重A(1)-B(3)-C(6)-D(10)则4个元素的的权重管辖区间分别为[0,1)[1,3)[3,6)[6,10)<br>
* 然后随机出一个[0,10)之间的随机数落在哪个区间则该区间之后的元素即为按权重命中的元素<br>
*
* <p>
* 参考博客<a href="https://www.cnblogs.com/waterystone/p/5708063.html">https://www.cnblogs.com/waterystone/p/5708063.html</a>
* <p>
*
* @param <T> 权重随机获取的对象类型
* @author looly
* @since 3.3.0
*/
public class WeightRandom<T> implements Serializable {
private static final long serialVersionUID = -8244697995702786499L;
private final TreeMap<Double, T> weightMap;
/**
* 创建权重随机获取器
*
* @param <T> 权重随机获取的对象类型
* @return WeightRandom
*/
public static <T> WeightRandom<T> of() {
return new WeightRandom<>();
}
// ---------------------------------------------------------------------------------- Constructor start
/**
* 构造
*/
public WeightRandom() {
weightMap = new TreeMap<>();
}
/**
* 构造
*
* @param weightObj 带有权重的对象
*/
public WeightRandom(final WeightObj<T> weightObj) {
this();
if(null != weightObj) {
add(weightObj);
}
}
/**
* 构造
*
* @param weightObjs 带有权重的对象
*/
public WeightRandom(final Iterable<WeightObj<T>> weightObjs) {
this();
if(CollUtil.isNotEmpty(weightObjs)) {
for (final WeightObj<T> weightObj : weightObjs) {
add(weightObj);
}
}
}
/**
* 构造
*
* @param weightObjs 带有权重的对象
*/
public WeightRandom(final WeightObj<T>[] weightObjs) {
this();
for (final WeightObj<T> weightObj : weightObjs) {
add(weightObj);
}
}
// ---------------------------------------------------------------------------------- Constructor end
/**
* 增加对象
*
* @param obj 对象
* @param weight 权重
* @return this
*/
public WeightRandom<T> add(final T obj, final double weight) {
return add(new WeightObj<>(obj, weight));
}
/**
* 增加对象权重
*
* @param weightObj 权重对象
* @return this
*/
public WeightRandom<T> add(final WeightObj<T> weightObj) {
if(null != weightObj) {
final double weight = weightObj.getWeight();
if(weightObj.getWeight() > 0) {
final double lastWeight = (this.weightMap.size() == 0) ? 0 : this.weightMap.lastKey();
this.weightMap.put(weight + lastWeight, weightObj.getObj());// 权重累加
}
}
return this;
}
/**
* 清空权重表
*
* @return this
*/
public WeightRandom<T> clear() {
if(null != this.weightMap) {
this.weightMap.clear();
}
return this;
}
/**
* 下一个随机对象
*
* @return 随机对象
*/
public T next() {
if(MapUtil.isEmpty(this.weightMap)) {
return null;
}
final Random random = RandomUtil.getRandom();
final double randomWeight = this.weightMap.lastKey() * random.nextDouble();
final SortedMap<Double, T> tailMap = this.weightMap.tailMap(randomWeight, false);
return this.weightMap.get(tailMap.firstKey());
}
/**
* 带有权重的对象包装
*
* @author looly
*
* @param <T> 对象类型
*/
public static class WeightObj<T> {
/** 对象 */
private T obj;
/** 权重 */
private final double weight;
/**
* 构造
*
* @param obj 对象
* @param weight 权重
*/
public WeightObj(final T obj, final double weight) {
this.obj = obj;
this.weight = weight;
}
/**
* 获取对象
*
* @return 对象
*/
public T getObj() {
return obj;
}
/**
* 设置对象
*
* @param obj 对象
*/
public void setObj(final T obj) {
this.obj = obj;
}
/**
* 获取权重
*
* @return 权重
*/
public double getWeight() {
return weight;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((obj == null) ? 0 : obj.hashCode());
final long temp;
temp = Double.doubleToLongBits(weight);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final WeightObj<?> other = (WeightObj<?>) obj;
if (this.obj == null) {
if (other.obj != null) {
return false;
}
} else if (!this.obj.equals(other.obj)) {
return false;
}
return Double.doubleToLongBits(weight) == Double.doubleToLongBits(other.weight);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2023 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:
* http://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.lang.selector;
/**
* 选择器接口
*
* @param <T> 选择对象类型
* @author looly
* @since 6.0.0
*/
public interface Selector<T> {
/**
* 选择下一个对象
*
* @return 下一个对象
*/
T next();
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2023 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:
* http://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.lang.selector;
/**
* 平滑权重对象
*
* @param <T> 对象类型
* @author Wind, Loolt
*/
public class SmoothWeightObj<T> extends WeightObj<T> {
private int currentWeight;
/**
* 构造
*
* @param obj 对象
* @param weight 权重
*/
public SmoothWeightObj(final T obj, final int weight) {
this(obj, weight, 0);
}
/**
* 构造
*
* @param obj 对象
* @param weight 权重
* @param currentWeight 当前权重
*/
public SmoothWeightObj(final T obj, final int weight, final int currentWeight) {
super(obj, weight);
this.currentWeight = currentWeight;
}
/**
* 获取当前权重
*
* @return int 临时权重
*/
public int getCurrentWeight() {
return currentWeight;
}
/**
* setCurrentWeight
* <p>设置当前权重
*
* @param currentWeight 权重值
*/
public void setCurrentWeight(final int currentWeight) {
this.currentWeight = currentWeight;
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright (c) 2023 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:
* http://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.lang.selector;
import org.dromara.hutool.core.collection.CollUtil;
import java.util.ArrayList;
import java.util.List;
/**
* 平滑加权轮询选择器
*
* <p>
* 来自https://gitee.com/dromara/hutool/pulls/982/
* </p>
* <p>
* 介绍https://cloud.tencent.com/developer/beta/article/1680928
* </p>
*
* <p>
* 思路: 比如 A : 5 , B : 3 , C : 2 (服务器 A,B,C 对应权重分别是 5,3,2)
* ip: A,B,C
* weight: 5,3,2 (计算得到 totalWeight = 10)
* currentWeight: 0,0,0 (当前ip的初始权重都为0)
* <pre>
* 请求次数: | currentWeight = currentWeight + weight | 最大权重为 | 返回的ip为 | 最大的权重 - totalWeight,其余不变
* 1 | 5,3,2 (0,0,0 + 5,3,2) | 5 | A | -5,3,2
* 2 | 0,6,4 (-5,3,2 + 5,3,2) | 6 | B | 0,-4,4
* 3 | 5,-1,6 (0,-4,4 + 5,3,2) | 6 | C | 5,-1,-4
* 4 | 10,2,-2 (5,-1,-4 + 5,3,2) | 10 | A | 0,2,-2
* 5 | 5,5,0 | 5 | A | -5,5,0
* 6 | 0,8,2 | 8 | B | 0,-2,2
* 7 | 5,1,4 | 5 | A | -5,1,4
* 8 | 0,4,6 | 6 | C | 0,4,-4
* 9 | 5,7,-2 | 7 | B | 5,-3,-2
* 10 | 10,0,0 | 10 | A | 0,0,0
* </pre>
*
* 至此结束: 可以看到负载轮询的策略是: A,B,C,A,A,B,A,C,B,A,
*
* @param <T> 对象类型
* @author :Wind, Looly
*/
public class SmoothWeightSelector<T> implements Selector<T> {
/**
* 创建平滑加权获取器
*
* @param <T> 对象类型
* @return SmoothSelector
*/
public static <T> SmoothWeightSelector<T> of() {
return new SmoothWeightSelector<>();
}
private final List<SmoothWeightObj<T>> objList;
// region ----- Constructors
/**
* 构造
*/
public SmoothWeightSelector() {
this.objList = new ArrayList<>();
}
/**
* 构造
*
* @param weightObjList 权重对象列表
*/
public SmoothWeightSelector(final Iterable<? extends WeightObj<T>> weightObjList) {
this();
for (final WeightObj<T> weightObj : weightObjList) {
add(weightObj);
}
}
// endregion
/**
* 增加对象
*
* @param obj 对象
* @param weight 权重
* @return this
*/
public SmoothWeightSelector<T> add(final T obj, final int weight) {
return add(new SmoothWeightObj<>(obj, weight));
}
/**
* 增加权重对象
*
* @param weightObj 权重对象
* @return this
*/
public SmoothWeightSelector<T> add(final WeightObj<T> weightObj) {
final SmoothWeightObj<T> smoothWeightObj;
if (weightObj instanceof SmoothWeightObj) {
smoothWeightObj = (SmoothWeightObj<T>) weightObj;
} else {
smoothWeightObj = new SmoothWeightObj<>(weightObj.obj, weightObj.weight);
}
this.objList.add(smoothWeightObj);
return this;
}
/**
* 通过平滑加权方法获取列表中的当前对象
*
* @return 选中的对象
*/
@Override
public T next() {
if (CollUtil.isEmpty(this.objList)) {
return null;
}
int totalWeight = 0;
SmoothWeightObj<T> selected = null;
for (final SmoothWeightObj<T> obj : objList) {
totalWeight += obj.getWeight();
final int currentWeight = obj.getCurrentWeight() + obj.getWeight();
obj.setCurrentWeight(currentWeight);
if (null == selected || currentWeight > selected.getCurrentWeight()) {
selected = obj;
}
}
if (null == selected) {
return null;
}
// 更新选择的对象的当前权重并返回其地址
selected.setCurrentWeight(selected.getCurrentWeight() - totalWeight);
return selected.getObj();
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2023 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:
* http://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.lang.selector;
import java.util.Objects;
/**
* 带有权重的对象包装
*
* @author looly
*
* @param <T> 对象类型
*/
public class WeightObj<T> {
/** 对象 */
protected T obj;
/** 权重 */
protected final int weight;
/**
* 构造
*
* @param obj 对象
* @param weight 权重
*/
public WeightObj(final T obj, final int weight) {
this.obj = obj;
this.weight = weight;
}
/**
* 获取对象
*
* @return 对象
*/
public T getObj() {
return obj;
}
/**
* 设置对象
*
* @param obj 对象
*/
public void setObj(final T obj) {
this.obj = obj;
}
/**
* 获取权重
*
* @return 权重
*/
public int getWeight() {
return weight;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final WeightObj<?> weightObj = (WeightObj<?>) o;
return weight == weightObj.weight && Objects.equals(obj, weightObj.obj);
}
@Override
public int hashCode() {
return Objects.hash(obj, weight);
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright (c) 2023 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:
* http://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.lang.selector;
import org.dromara.hutool.core.collection.CollUtil;
import java.io.Serializable;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* 权重随机选择算法实现<br>
* <p>
* 平时经常会遇到权重随机算法从不同权重的N个元素中随机选择一个并使得总体选择结果是按照权重分布的如广告投放负载均衡等
* </p>
* <p>
* 如有4个元素ABCD权重分别为1234随机结果中A:B:C:D的比例要为1:2:3:4<br>
* </p>
* 总体思路累加每个元素的权重A(1)-B(3)-C(6)-D(10)则4个元素的的权重管辖区间分别为[0,1)[1,3)[3,6)[6,10)<br>
* 然后随机出一个[0,10)之间的随机数落在哪个区间则该区间之后的元素即为按权重命中的元素<br>
*
* <p>
* 参考博客https://www.cnblogs.com/waterystone/p/5708063.html
*
* @param <T> 权重随机获取的对象类型
* @author looly
* @since 3.3.0
*/
public class WeightRandomSelector<T> implements Selector<T>, Serializable {
private static final long serialVersionUID = -8244697995702786499L;
/**
* 创建权重随机获取器
*
* @param <T> 权重随机获取的对象类型
* @return WeightRandomSelector
*/
public static <T> WeightRandomSelector<T> of() {
return new WeightRandomSelector<>();
}
private final TreeMap<Integer, T> weightMap;
// region ----- Constructors
/**
* 构造
*/
public WeightRandomSelector() {
weightMap = new TreeMap<>();
}
/**
* 构造
*
* @param weightObj 带有权重的对象
*/
public WeightRandomSelector(final WeightObj<T> weightObj) {
this();
if (null != weightObj) {
add(weightObj);
}
}
/**
* 构造
*
* @param weightObjs 带有权重的对象
*/
public WeightRandomSelector(final Iterable<WeightObj<T>> weightObjs) {
this();
if (CollUtil.isNotEmpty(weightObjs)) {
for (final WeightObj<T> weightObj : weightObjs) {
add(weightObj);
}
}
}
/**
* 构造
*
* @param weightObjs 带有权重的对象
*/
public WeightRandomSelector(final WeightObj<T>[] weightObjs) {
this();
for (final WeightObj<T> weightObj : weightObjs) {
add(weightObj);
}
}
// endregion
/**
* 增加对象
*
* @param obj 对象
* @param weight 权重
* @return this
*/
public WeightRandomSelector<T> add(final T obj, final int weight) {
return add(new WeightObj<>(obj, weight));
}
/**
* 增加对象权重
*
* @param weightObj 权重对象
* @return this
*/
public WeightRandomSelector<T> add(final WeightObj<T> weightObj) {
if (null != weightObj) {
final int weight = weightObj.getWeight();
if (weight > 0) {
final int lastWeight = this.weightMap.isEmpty() ? 0 : this.weightMap.lastKey();
this.weightMap.put(weight + lastWeight, weightObj.getObj());// 权重累加
}
}
return this;
}
/**
* 清空权重表
*
* @return this
*/
public WeightRandomSelector<T> clear() {
if (null != this.weightMap) {
this.weightMap.clear();
}
return this;
}
/**
* 下一个随机对象
*
* @return 随机对象
*/
@Override
public T next() {
final int randomWeight = (int) (this.weightMap.lastKey() * Math.random());
final SortedMap<Integer, T> tailMap = this.weightMap.tailMap(randomWeight, false);
return this.weightMap.get(tailMap.firstKey());
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 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:
* http://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.
*/
/**
* 选择器相关封装包括
* <ul>
* <li>{@link org.dromara.hutool.core.lang.selector.WeightRandomSelector}</li>
* </ul>
*
* @author looly
*/
package org.dromara.hutool.core.lang.selector;

View File

@ -40,18 +40,18 @@ import java.util.function.BiConsumer;
* concurrency for updates, and a maximum capacity to bound the map by. This * concurrency for updates, and a maximum capacity to bound the map by. This
* implementation differs from {@link ConcurrentHashMap} in that it maintains a * implementation differs from {@link ConcurrentHashMap} in that it maintains a
* page replacement algorithm that is used to evict an entry when the map has * page replacement algorithm that is used to evict an entry when the map has
* exceeded its capacity. Unlike the <tt>Java Collections Framework</tt>, this * exceeded its capacity. Unlike the {@code Java Collections Framework}, this
* map does not have a publicly visible constructor and instances are created * map does not have a publicly visible constructor and instances are created
* through a {@link Builder}. * through a {@link Builder}.
* <p> * <p>
* An entry is evicted from the map when the <tt>weighted capacity</tt> exceeds * An entry is evicted from the map when the {@code weighted capacity} exceeds
* its <tt>maximum weighted capacity</tt> threshold. A {@link EntryWeigher} * its {@code maximum weighted capacity} threshold. A {@link EntryWeigher}
* determines how many units of capacity that an entry consumes. The default * determines how many units of capacity that an entry consumes. The default
* weigher assigns each value a weight of <tt>1</tt> to bound the map by the * weigher assigns each value a selector of {@code 1} to bound the map by the
* total number of key-value pairs. A map that holds collections may choose to * total number of key-value pairs. A map that holds collections may choose to
* weigh values by the number of elements in the collection and bound the map * weigh values by the number of elements in the collection and bound the map
* by the total number of elements that it contains. A change to a value that * by the total number of elements that it contains. A change to a value that
* modifies its weight requires that an update operation is performed on the * modifies its selector requires that an update operation is performed on the
* map. * map.
* <p> * <p>
* An {@link BiConsumer} may be supplied for notification when an entry * An {@link BiConsumer} may be supplied for notification when an entry
@ -63,7 +63,7 @@ import java.util.function.BiConsumer;
* operation asynchronously, such as by submitting a task to an * operation asynchronously, such as by submitting a task to an
* {@link java.util.concurrent.ExecutorService}. * {@link java.util.concurrent.ExecutorService}.
* <p> * <p>
* The <tt>concurrency level</tt> determines the number of threads that can * The {@code concurrency level} determines the number of threads that can
* concurrently modify the table. Using a significantly higher or lower value * concurrently modify the table. Using a significantly higher or lower value
* than needed can waste space or lead to thread contention, but an estimate * than needed can waste space or lead to thread contention, but an estimate
* within an order of magnitude of the ideal value does not usually have a * within an order of magnitude of the ideal value does not usually have a
@ -75,7 +75,7 @@ import java.util.function.BiConsumer;
* interfaces. * interfaces.
* <p> * <p>
* Like {@link Hashtable} but unlike {@link HashMap}, this class * Like {@link Hashtable} but unlike {@link HashMap}, this class
* does <em>not</em> allow <tt>null</tt> to be used as a key or value. Unlike * does <em>not</em> allow {@code null} to be used as a key or value. Unlike
* {@link LinkedHashMap}, this class does <em>not</em> provide * {@link LinkedHashMap}, this class does <em>not</em> provide
* predictable iteration order. A snapshot of the keys and entries may be * predictable iteration order. A snapshot of the keys and entries may be
* obtained in ascending and descending order of retention. * obtained in ascending and descending order of retention.
@ -112,16 +112,16 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
* *
* Due to a lack of a strict ordering guarantee, a task can be executed * Due to a lack of a strict ordering guarantee, a task can be executed
* out-of-order, such as a removal followed by its addition. The state of the * out-of-order, such as a removal followed by its addition. The state of the
* entry is encoded within the value's weight. * entry is encoded within the value's selector.
* *
* Alive: The entry is in both the hash-table and the page replacement policy. * Alive: The entry is in both the hash-table and the page replacement policy.
* This is represented by a positive weight. * This is represented by a positive selector.
* *
* Retired: The entry is not in the hash-table and is pending removal from the * Retired: The entry is not in the hash-table and is pending removal from the
* page replacement policy. This is represented by a negative weight. * page replacement policy. This is represented by a negative selector.
* *
* Dead: The entry is not in the hash-table and is not in the page replacement * Dead: The entry is not in the hash-table and is not in the page replacement
* policy. This is represented by a weight of zero. * policy. This is represented by a selector of zero.
* *
* The Least Recently Used page replacement algorithm was chosen due to its * The Least Recently Used page replacement algorithm was chosen due to its
* simplicity, high hit rate, and ability to be implemented with O(1) time * simplicity, high hit rate, and ability to be implemented with O(1) time
@ -297,7 +297,7 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
final Node<K, V> node = evictionDeque.poll(); final Node<K, V> node = evictionDeque.poll();
// If weighted values are used, then the pending operations will adjust // If weighted values are used, then the pending operations will adjust
// the size to reflect the correct weight // the size to reflect the correct selector
if (node == null) { if (node == null) {
return; return;
} }
@ -466,8 +466,8 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* Attempts to transition the node from the <tt>alive</tt> state to the * Attempts to transition the node from the {@code alive} state to the
* <tt>retired</tt> state. * {@code retired} state.
* *
* @param node the entry in the page replacement policy * @param node the entry in the page replacement policy
* @param expect the expected weighted value * @param expect the expected weighted value
@ -482,8 +482,8 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* Atomically transitions the node from the <tt>alive</tt> state to the * Atomically transitions the node from the {@code alive} state to the
* <tt>retired</tt> state, if a valid transition. * {@code retired} state, if a valid transition.
* *
* @param node the entry in the page replacement policy * @param node the entry in the page replacement policy
*/ */
@ -501,8 +501,8 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* Atomically transitions the node to the <tt>dead</tt> state and decrements * Atomically transitions the node to the {@code dead} state and decrements
* the <tt>weightedSize</tt>. * the {@code weightedSize}.
* *
* @param node the entry in the page replacement policy * @param node the entry in the page replacement policy
*/ */
@ -608,7 +608,7 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
/** /**
* Returns the weighted size of this map. * Returns the weighted size of this map.
* *
* @return the combined weight of the values in this map * @return the combined selector of the values in this map
*/ */
public long weightedSize() { public long weightedSize() {
return Math.max(0, weightedSize.get()); return Math.max(0, weightedSize.get());
@ -1097,7 +1097,7 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* A value, its weight, and the entry's status. * A value, its selector, and the entry's status.
*/ */
static final class WeightedValue<V> { static final class WeightedValue<V> {
final int weight; final int weight;
@ -1178,7 +1178,7 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* Retrieves the value held by the current <tt>WeightedValue</tt>. * Retrieves the value held by the current {@code WeightedValue}.
*/ */
V getValue() { V getValue() {
return get().value; return get().value;
@ -1401,7 +1401,7 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* A weigher that enforces that the weight falls within a valid range. * A weigher that enforces that the selector falls within a valid range.
*/ */
static final class BoundedEntryWeigher<K, V> implements EntryWeigher<K, V>, Serializable { static final class BoundedEntryWeigher<K, V> implements EntryWeigher<K, V>, Serializable {
private static final long serialVersionUID = 1; private static final long serialVersionUID = 1;
@ -1520,7 +1520,7 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* Specifies the initial capacity of the hash table (default <tt>16</tt>). * Specifies the initial capacity of the hash table (default {@code 16}).
* This is the number of key-value pairs that the hash table can hold * This is the number of key-value pairs that the hash table can hold
* before a resize operation is required. * before a resize operation is required.
* *
@ -1553,7 +1553,7 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
/** /**
* Specifies the estimated number of concurrently updating threads. The * Specifies the estimated number of concurrently updating threads. The
* implementation performs internal sizing to try to accommodate this many * implementation performs internal sizing to try to accommodate this many
* threads (default <tt>16</tt>). * threads (default {@code 16}).
* *
* @param concurrencyLevel the estimated number of concurrently updating * @param concurrencyLevel the estimated number of concurrently updating
* threads * threads
@ -1584,9 +1584,9 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
/** /**
* Specifies an algorithm to determine how many the units of capacity a * Specifies an algorithm to determine how many the units of capacity a
* value consumes. The default algorithm bounds the map by the number of * value consumes. The default algorithm bounds the map by the number of
* key-value pairs by giving each entry a weight of <tt>1</tt>. * key-value pairs by giving each entry a selector of {@code 1}.
* *
* @param weigher the algorithm to determine a value's weight * @param weigher the algorithm to determine a value's selector
* @return this * @return this
* @throws NullPointerException if the weigher is null * @throws NullPointerException if the weigher is null
*/ */
@ -1600,9 +1600,9 @@ public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
/** /**
* Specifies an algorithm to determine how many the units of capacity an * Specifies an algorithm to determine how many the units of capacity an
* entry consumes. The default algorithm bounds the map by the number of * entry consumes. The default algorithm bounds the map by the number of
* key-value pairs by giving each entry a weight of <tt>1</tt>. * key-value pairs by giving each entry a selector of {@code 1}.
* *
* @param weigher the algorithm to determine a entry's weight * @param weigher the algorithm to determine a entry's selector
* @return this * @return this
* @throws NullPointerException if the weigher is null * @throws NullPointerException if the weigher is null
*/ */

View File

@ -16,7 +16,7 @@
package org.dromara.hutool.core.map.concurrent; package org.dromara.hutool.core.map.concurrent;
/** /**
* A class that can determine the weight of an entry. The total weight threshold * A class that can determine the selector of an entry. The total selector threshold
* is used to determine when an eviction is required. * is used to determine when an eviction is required.
* *
* @param <K> 键类型 * @param <K> 键类型
@ -28,12 +28,12 @@ package org.dromara.hutool.core.map.concurrent;
public interface EntryWeigher<K, V> { public interface EntryWeigher<K, V> {
/** /**
* Measures an entry's weight to determine how many units of capacity that * Measures an entry's selector to determine how many units of capacity that
* the key and value consumes. An entry must consume a minimum of one unit. * the key and value consumes. An entry must consume a minimum of one unit.
* *
* @param key the key to weigh * @param key the key to weigh
* @param value the value to weigh * @param value the value to weigh
* @return the entry's weight * @return the entry's selector
*/ */
int weightOf(K key, V value); int weightOf(K key, V value);
} }

View File

@ -16,7 +16,7 @@
package org.dromara.hutool.core.map.concurrent; package org.dromara.hutool.core.map.concurrent;
/** /**
* A class that can determine the weight of a value. The total weight threshold * A class that can determine the selector of a value. The total selector threshold
* is used to determine when an eviction is required. * is used to determine when an eviction is required.
* *
* @param <V> 值类型 * @param <V> 值类型
@ -27,11 +27,11 @@ package org.dromara.hutool.core.map.concurrent;
public interface Weigher<V> { public interface Weigher<V> {
/** /**
* Measures an object's weight to determine how many units of capacity that * Measures an object's selector to determine how many units of capacity that
* the value consumes. A value must consume a minimum of one unit. * the value consumes. A value must consume a minimum of one unit.
* *
* @param value the object to weigh * @param value the object to weigh
* @return the object's weight * @return the object's selector
*/ */
int weightOf(V value); int weightOf(V value);
} }

View File

@ -38,8 +38,8 @@ public final class Weighers {
} }
/** /**
* A entry weigher backed by the specified weigher. The weight of the value * A entry weigher backed by the specified weigher. The selector of the value
* determines the weight of the entry. * determines the selector of the entry.
* *
* @param weigher the weigher to be "wrapped" in a entry weigher. * @param weigher the weigher to be "wrapped" in a entry weigher.
* @param <K> 键类型 * @param <K> 键类型
@ -54,7 +54,7 @@ public final class Weighers {
} }
/** /**
* A weigher where an entry has a weight of <tt>1</tt>. A map bounded with * A weigher where an entry has a selector of <tt>1</tt>. A map bounded with
* this weigher will evict when the number of key-value pairs exceeds the * this weigher will evict when the number of key-value pairs exceeds the
* capacity. * capacity.
* *
@ -68,7 +68,7 @@ public final class Weighers {
} }
/** /**
* A weigher where a value has a weight of <tt>1</tt>. A map bounded with * A weigher where a value has a selector of <tt>1</tt>. A map bounded with
* this weigher will evict when the number of key-value pairs exceeds the * this weigher will evict when the number of key-value pairs exceeds the
* capacity. * capacity.
* *
@ -81,17 +81,17 @@ public final class Weighers {
} }
/** /**
* A weigher where the value is a byte array and its weight is the number of * A weigher where the value is a byte array and its selector is the number of
* bytes. A map bounded with this weigher will evict when the number of bytes * bytes. A map bounded with this weigher will evict when the number of bytes
* exceeds the capacity rather than the number of key-value pairs in the map. * exceeds the capacity rather than the number of key-value pairs in the map.
* This allows for restricting the capacity based on the memory-consumption * This allows for restricting the capacity based on the memory-consumption
* and is primarily for usage by dedicated caching servers that hold the * and is primarily for usage by dedicated caching servers that hold the
* serialized data. * serialized data.
* <p> * <p>
* A value with a weight of <tt>0</tt> will be rejected by the map. If a value * A value with a selector of <tt>0</tt> will be rejected by the map. If a value
* with this weight can occur then the caller should eagerly evaluate the * with this selector can occur then the caller should eagerly evaluate the
* value and treat it as a removal operation. Alternatively, a custom weigher * value and treat it as a removal operation. Alternatively, a custom weigher
* may be specified on the map to assign an empty value a positive weight. * may be specified on the map to assign an empty value a positive selector.
* *
* @return A weigher where each byte takes one unit of capacity. * @return A weigher where each byte takes one unit of capacity.
*/ */
@ -100,16 +100,16 @@ public final class Weighers {
} }
/** /**
* A weigher where the value is a {@link Iterable} and its weight is the * A weigher where the value is a {@link Iterable} and its selector is the
* number of elements. This weigher only should be used when the alternative * number of elements. This weigher only should be used when the alternative
* {@link #collection()} weigher cannot be, as evaluation takes O(n) time. A * {@link #collection()} weigher cannot be, as evaluation takes O(n) time. A
* map bounded with this weigher will evict when the total number of elements * map bounded with this weigher will evict when the total number of elements
* exceeds the capacity rather than the number of key-value pairs in the map. * exceeds the capacity rather than the number of key-value pairs in the map.
* <p> * <p>
* A value with a weight of <tt>0</tt> will be rejected by the map. If a value * A value with a selector of <tt>0</tt> will be rejected by the map. If a value
* with this weight can occur then the caller should eagerly evaluate the * with this selector can occur then the caller should eagerly evaluate the
* value and treat it as a removal operation. Alternatively, a custom weigher * value and treat it as a removal operation. Alternatively, a custom weigher
* may be specified on the map to assign an empty value a positive weight. * may be specified on the map to assign an empty value a positive selector.
* *
* @param <E> 元素类型 * @param <E> 元素类型
* @return A weigher where each element takes one unit of capacity. * @return A weigher where each element takes one unit of capacity.
@ -120,15 +120,15 @@ public final class Weighers {
} }
/** /**
* A weigher where the value is a {@link Collection} and its weight is the * A weigher where the value is a {@link Collection} and its selector is the
* number of elements. A map bounded with this weigher will evict when the * number of elements. A map bounded with this weigher will evict when the
* total number of elements exceeds the capacity rather than the number of * total number of elements exceeds the capacity rather than the number of
* key-value pairs in the map. * key-value pairs in the map.
* <p> * <p>
* A value with a weight of <tt>0</tt> will be rejected by the map. If a value * A value with a selector of <tt>0</tt> will be rejected by the map. If a value
* with this weight can occur then the caller should eagerly evaluate the * with this selector can occur then the caller should eagerly evaluate the
* value and treat it as a removal operation. Alternatively, a custom weigher * value and treat it as a removal operation. Alternatively, a custom weigher
* may be specified on the map to assign an empty value a positive weight. * may be specified on the map to assign an empty value a positive selector.
* *
* @param <E> 元素类型 * @param <E> 元素类型
* @return A weigher where each element takes one unit of capacity. * @return A weigher where each element takes one unit of capacity.
@ -139,15 +139,15 @@ public final class Weighers {
} }
/** /**
* A weigher where the value is a {@link List} and its weight is the number * A weigher where the value is a {@link List} and its selector is the number
* of elements. A map bounded with this weigher will evict when the total * of elements. A map bounded with this weigher will evict when the total
* number of elements exceeds the capacity rather than the number of * number of elements exceeds the capacity rather than the number of
* key-value pairs in the map. * key-value pairs in the map.
* <p> * <p>
* A value with a weight of <tt>0</tt> will be rejected by the map. If a value * A value with a selector of <tt>0</tt> will be rejected by the map. If a value
* with this weight can occur then the caller should eagerly evaluate the * with this selector can occur then the caller should eagerly evaluate the
* value and treat it as a removal operation. Alternatively, a custom weigher * value and treat it as a removal operation. Alternatively, a custom weigher
* may be specified on the map to assign an empty value a positive weight. * may be specified on the map to assign an empty value a positive selector.
* *
* @param <E> 元素类型 * @param <E> 元素类型
* @return A weigher where each element takes one unit of capacity. * @return A weigher where each element takes one unit of capacity.
@ -158,15 +158,15 @@ public final class Weighers {
} }
/** /**
* A weigher where the value is a {@link Set} and its weight is the number * A weigher where the value is a {@link Set} and its selector is the number
* of elements. A map bounded with this weigher will evict when the total * of elements. A map bounded with this weigher will evict when the total
* number of elements exceeds the capacity rather than the number of * number of elements exceeds the capacity rather than the number of
* key-value pairs in the map. * key-value pairs in the map.
* <p> * <p>
* A value with a weight of <tt>0</tt> will be rejected by the map. If a value * A value with a selector of <tt>0</tt> will be rejected by the map. If a value
* with this weight can occur then the caller should eagerly evaluate the * with this selector can occur then the caller should eagerly evaluate the
* value and treat it as a removal operation. Alternatively, a custom weigher * value and treat it as a removal operation. Alternatively, a custom weigher
* may be specified on the map to assign an empty value a positive weight. * may be specified on the map to assign an empty value a positive selector.
* *
* @param <E> 元素类型 * @param <E> 元素类型
* @return A weigher where each element takes one unit of capacity. * @return A weigher where each element takes one unit of capacity.
@ -177,15 +177,15 @@ public final class Weighers {
} }
/** /**
* A weigher where the value is a {@link Map} and its weight is the number of * A weigher where the value is a {@link Map} and its selector is the number of
* entries. A map bounded with this weigher will evict when the total number of * entries. A map bounded with this weigher will evict when the total number of
* entries across all values exceeds the capacity rather than the number of * entries across all values exceeds the capacity rather than the number of
* key-value pairs in the map. * key-value pairs in the map.
* <p> * <p>
* A value with a weight of <tt>0</tt> will be rejected by the map. If a value * A value with a selector of <tt>0</tt> will be rejected by the map. If a value
* with this weight can occur then the caller should eagerly evaluate the * with this selector can occur then the caller should eagerly evaluate the
* value and treat it as a removal operation. Alternatively, a custom weigher * value and treat it as a removal operation. Alternatively, a custom weigher
* may be specified on the map to assign an empty value a positive weight. * may be specified on the map to assign an empty value a positive selector.
* *
* @param <K> 键类型 * @param <K> 键类型
* @param <V> 值类型 * @param <V> 值类型

View File

@ -30,7 +30,7 @@ public class TreeNodeConfig implements Serializable {
// 属性名配置字段 // 属性名配置字段
private String idKey = "id"; private String idKey = "id";
private String parentIdKey = "parentId"; private String parentIdKey = "parentId";
private String weightKey = "weight"; private String weightKey = "selector";
private String nameKey = "name"; private String nameKey = "name";
private String childrenKey = "children"; private String childrenKey = "children";
// 可以配置递归深度 从0开始计算 默认此配置为空,即不限制 // 可以配置递归深度 从0开始计算 默认此配置为空,即不限制

View File

@ -20,8 +20,8 @@ import org.dromara.hutool.core.date.DateTime;
import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.exception.HutoolException; import org.dromara.hutool.core.exception.HutoolException;
import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.WeightRandom; import org.dromara.hutool.core.lang.selector.WeightObj;
import org.dromara.hutool.core.lang.WeightRandom.WeightObj; import org.dromara.hutool.core.lang.selector.WeightRandomSelector;
import org.dromara.hutool.core.math.NumberUtil; import org.dromara.hutool.core.math.NumberUtil;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
@ -29,13 +29,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
/** /**
@ -637,11 +631,11 @@ public class RandomUtil {
* *
* @param <T> 随机对象类型 * @param <T> 随机对象类型
* @param weightObjs 带有权重的对象列表 * @param weightObjs 带有权重的对象列表
* @return {@link WeightRandom} * @return {@link WeightRandomSelector}
* @since 4.0.3 * @since 4.0.3
*/ */
public static <T> WeightRandom<T> weightRandom(final WeightObj<T>[] weightObjs) { public static <T> WeightRandomSelector<T> weightRandom(final WeightObj<T>[] weightObjs) {
return new WeightRandom<>(weightObjs); return new WeightRandomSelector<>(weightObjs);
} }
/** /**
@ -649,11 +643,11 @@ public class RandomUtil {
* *
* @param <T> 随机对象类型 * @param <T> 随机对象类型
* @param weightObjs 带有权重的对象列表 * @param weightObjs 带有权重的对象列表
* @return {@link WeightRandom} * @return {@link WeightRandomSelector}
* @since 4.0.3 * @since 4.0.3
*/ */
public static <T> WeightRandom<T> weightRandom(final Iterable<WeightObj<T>> weightObjs) { public static <T> WeightRandomSelector<T> weightRandom(final Iterable<WeightObj<T>> weightObjs) {
return new WeightRandom<>(weightObjs); return new WeightRandomSelector<>(weightObjs);
} }
/** /**

View File

@ -1,19 +0,0 @@
package org.dromara.hutool.core.lang;
import org.dromara.hutool.core.collection.ListUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class WeightRandomTest {
@Test
public void weightRandomTest() {
final WeightRandom<String> random = WeightRandom.of();
random.add("A", 10);
random.add("B", 50);
random.add("C", 100);
final String result = random.next();
Assertions.assertTrue(ListUtil.of("A", "B", "C").contains(result));
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 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:
* http://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.lang.selector;
import org.dromara.hutool.core.collection.ListUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class WeightRandomSelectorTest {
@Test
public void weightRandomTest() {
final WeightRandomSelector<String> random = WeightRandomSelector.of();
random.add("A", 10);
random.add("B", 50);
random.add("C", 100);
final String result = random.next();
Assertions.assertTrue(ListUtil.of("A", "B", "C").contains(result));
}
}