惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
aimingoo的专栏
aimingoo的专栏
B
Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
GbyAI
GbyAI
阮一峰的网络日志
阮一峰的网络日志
Engineering at Meta
Engineering at Meta
IT之家
IT之家
V
Visual Studio Blog
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
A
About on SuperTechFans
博客园 - 聂微东
Blog — PlanetScale
Blog — PlanetScale
N
News and Events Feed by Topic
A
Arctic Wolf
WordPress大学
WordPress大学
小众软件
小众软件
C
CERT Recently Published Vulnerability Notes
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Y
Y Combinator Blog
T
Threat Research - Cisco Blogs
Latest news
Latest news
Simon Willison's Weblog
Simon Willison's Weblog
Cyberwarzone
Cyberwarzone
S
Schneier on Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
Lohrmann on Cybersecurity
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Privacy International News Feed
J
Java Code Geeks
Spread Privacy
Spread Privacy
宝玉的分享
宝玉的分享
I
Intezer
L
LangChain Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
G
GRAHAM CLULEY
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
The GitHub Blog
The GitHub Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
News and Events Feed by Topic
AWS News Blog
AWS News Blog
Attack and Defense Labs
Attack and Defense Labs
Security Archives - TechRepublic
Security Archives - TechRepublic
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

博客园 - VipSoft

MinerU - 将非结构化文档(PDF、图片、Office 文件等)转换为机器可读的 Markdown 和 JSON LangChain 入门 服务端部署-FastAPI LangChain 入门 LangSmith LangChain 入门 实战 - 食谱推荐 LangChain 入门 Memory 会话记忆 LangChain 入门 Tools 工具 LangChain 入门 Tools 工具 LangChain 入门 Prompts 提示词 LangChain 入门 Message 消息 LangChain 入门 Model 的初始化和调用 LangChain 入门 Agent 的基本运行机制 AI 0基础学习,名词解析 LangChain 和 LangGraph AI大模型知识体系 Dify — Workflow - 数据可视化 Dify — 连接MySQL配置 Dify — Chatflow - 数据库智能查询 Dify — Chatflow - 文档知识库 Dify — Agent 智能体 高安全券码、注册码生成 Dify — 文本生成应用 Dify — 聊天助手 -- 知识库 Windows 下 Docker 安装 Dify Ollama — 命令 Ollama — 为什么能够运行不同的模型 Ollama — 接口 Qwen — 自定义模型文件 Ollama Qwen — 安装测试 Ollama Windows 安装 & 指定安装目录 跟着AI学AI - 诊断结论信息抽取 - 批量处理脚本 跟着AI学AI - 诊断结论信息抽取 - 模型压缩与部署 跟着AI学AI - 诊断结论信息抽取 - 模型评估与调试 跟着AI学AI - 诊断结论信息抽取 - 模型训练 轻型民用无人驾驶航空器安全操控理论考试培训材料 FreeRedis Helper Windbg w3wp.DMP 内存分析 跟着AI学AI - 诊断结论信息抽取 - 数据增强 QQ 录屏软件 Java - 加权随机算法--Demo Java LoadBalanceUtil 负载均衡、轮询加权 SpringBoot 集成 IP2Region 获取IP地域信息 Windows 服务器和虚拟主机,创建.开头的文件夹 .well-known 跟着AI学AI - 诊断结论信息抽取 - 数据格式转换BERT训练格式 跟着AI学AI - 诊断结论信息抽取 - LabelStudio 标注 -- 结论标注 跟着AI学AI - 诊断结论信息抽取 - 学习路径 跟着AI学AI - UV 安装 数据标注工具 Label-Studio `VIRTUAL_ENV=venv` does not match the project environment 跟着AI学AI - 学习路径 - 命名实体识别(NER)和信息抽取(IE) 跟着AI学AI - 需要购买显卡吗? C# 无BOM的UTF-8编码 Vue ref reactive Vue - el-table 嵌套表 Spring `@Scheduled` 中这些参数的区别、组合和应用场景 Python 找出同步日志中的重复数据 Python - UV PyCharm 不能识别 .venv 的环境
Java - 加权随机算法 - 示例
VipSoft · 2026-03-20 · via 博客园 - VipSoft

Java LoadBalanceUtil 负载均衡、轮询加权 https://www.cnblogs.com/vipsoft/p/19728820
Java - 加权随机算法--Demo: https://www.cnblogs.com/vipsoft/p/19741845

示例

VideoInfo.java

public class VideoInfo {
    private String name;
    private int weight;

    public VideoInfo(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

    public String getName() { return name; }
    public int getWeight() { return weight; }
}

service.java

private static final Object lock = new Object();

private final Random random = new Random();

public VideoInfo getVideoInfo(List<VideoInfo> videoList) {
    synchronized (lock) {
        // 使用 TreeMap 自动按键排序
        TreeMap<Integer, List<VideoInfo>> weightMap = new TreeMap<>();
        int totalWeight = 0;
        // 构建权重累积和映射
        for (VideoInfo video : videoList) {
            totalWeight += video.getWeight();
            weightMap.computeIfAbsent(totalWeight, k -> new ArrayList<>()).add(video);
        }
        // 生成随机数 [0, totalWeight)
        int randomNum = random.nextInt(totalWeight);
        // 找到第一个大于 randomNum 的键
        Map.Entry<Integer, List<VideoInfo>> entry = weightMap.higherEntry(randomNum);
        if (entry != null) {
            List<VideoInfo> candidates = entry.getValue();
            // 如果该权重区间有多个视频,随机选择一个
            return candidates.get(random.nextInt(candidates.size()));
        }
    }
    return null;
}

Test

@Test
void getVideoInfo() {

    List<VideoInfo> videoList = Arrays.asList(
            getVideoInfo("视频A", 1),
            getVideoInfo("视频B", 2),
            getVideoInfo("视频C", 3),
            getVideoInfo("视频D", 2)
    );

    Map<String, Integer> countMap = new HashMap<>();
    for (int i = 0; i < 100; i++) {
        VideoInfo video = lotteryService.getVideoInfo(videoList);
        countMap.merge(video.getTitle(), 1, Integer::sum);
    }
    System.out.println("测试结果(100次):");
    countMap.forEach((name, count) ->
            System.out.printf("%s: %d次 (%.1f%%)%n", name, count, count / 10.0));
}
测试结果(100次):
视频B: 29次 (2.9%)
视频A: 12次 (1.2%)
视频D: 22次 (2.2%)
视频C: 37次 (3.7%)

累积权重 vs 单个权重

这边为什么是 weightMap.computeIfAbsent(totalWeight, 而不是 weightMap.computeIfAbsent(video.getWeight(),

具体例子说明

假设有视频列表:

  • 视频A:权重 1
  • 视频B:权重 2
  • 视频C:权重 2
  • 视频D:权重 3

如果使用单个权重(错误):

weightMap 会变成:
key=1 → [视频A]
key=2 → [视频B, 视频C]  // 视频B和C被错误地放在同一个区间!
key=3 → [视频D]

随机数区间划分:
[0,1) → key=1 → [视频A]
[1,2) → key=2 → [视频B, 视频C]  // 这不对!视频B应该占更大的区间
[2,3) → key=2 → [视频B, 视频C]  // 视频C也应该有自己的区间
[3,6) → key=3 → [视频D]  // 区间大小不对!

使用累积权重(正确):

计算过程:
totalWeight=1  → key=1 → [视频A]
totalWeight=3  → key=3 → [视频B]  // 1+2=3
totalWeight=5  → key=5 → [视频C]  // 3+2=5  
totalWeight=8  → key=8 → [视频D]  // 5+3=8

weightMap 变成:
key=1 → [视频A]
key=3 → [视频B]
key=5 → [视频C]
key=8 → [视频D]

随机数区间划分:
[0,1)  → key=1 → [视频A]  // A占1个单位
[1,3)  → key=3 → [视频B]  // B占2个单位
[3,5)  → key=5 → [视频C]  // C占2个单位  
[5,8)  → key=8 → [视频D]  // D占3个单位

可视化对比

总权重 = 8

正确方式(累积权重):
0    1         3         5         8
|----|---------|---------|---------|
  A      B         C         D
 1      2         2         3

错误方式(单个权重):
0    1    2    3    4    5    6    7    8
|----|----|----|----|----|----|----|----|
  A   B/C  B/C   D    D    D   ???  ???
 1    2    2    3    3    3

问题:
- B和C被压缩在同一个区间 [1,3)
- D的区间 [3,6) 虽然大小正确,但位置错误
- [6,8) 变成空白区间

总结

使用 totalWeight 作为 key 的原因:

  1. 区间唯一性:每个累积权重值都是唯一的,确保了每个视频有独立的区间
  2. 正确的概率分布:保证了每个视频被选中的概率等于其权重/总权重
  3. 随机映射准确:随机数可以直接映射到正确的视频区间

这就是为什么在权重随机算法中,我们通常使用累积权重而不是原始权重作为映射的键。

累积权重的话,要不要对 videoList 先做排序?

不需要排序! 这是一个很好的问题,让我解释为什么不需要排序:

为什么不需要排序?

累积权重算法天然地保持了原始顺序,而且顺序不影响概率分布。让我通过例子说明:

示例:不排序的情况

假设视频列表原始顺序:

List<VideoInfo> videoList = Arrays.asList(
    new VideoInfo("视频C", 3),  // 权重3
    new VideoInfo("视频A", 1),  // 权重1  
    new VideoInfo("视频B", 2)   // 权重2
);

累积权重计算过程:

// 第1个:视频C (权重3)
totalWeight = 3  → key=3 → [视频C]

// 第2个:视频A (权重1)  
totalWeight = 4  → key=4 → [视频A]

// 第3个:视频B (权重2)
totalWeight = 6  → key=6 → [视频B]

// 最终映射:
// key=3 → [视频C]
// key=4 → [视频A]  
// key=6 → [视频B]

随机数区间划分:

总权重 = 6

0    3         4         6
|----|---------|---------|
  C      A        B
 3      1        2

排序 vs 不排序的对比

不排序的情况(原始顺序:C, A, B)

区间划分:
[0,3) → 视频C (3个单位)
[3,4) → 视频A (1个单位)
[4,6) → 视频B (2个单位)

概率:C=3/6, A=1/6, B=2/6 ✅ 正确!

如果排序(A, B, C)

排序后:A(1), B(2), C(3)

区间划分:
[0,1) → 视频A (1个单位)
[1,3) → 视频B (2个单位)
[3,6) → 视频C (3个单位)

概率:A=1/6, B=2/6, C=3/6 ✅ 同样正确!

核心原理

累积权重算法的本质是将总权重区间按顺序分配给每个元素,无论元素顺序如何:

总权重区间 [0, totalWeight)
   ↓
按遍历顺序依次分配子区间:
元素1: [0, w1)
元素2: [w1, w1+w2)
元素3: [w1+w2, w1+w2+w3)
...

顺序只影响哪个元素对应哪个区间段,但不影响每个区间段的大小

代码验证

public class WeightOrderTest {
    public static void main(String[] args) {
        // 原始顺序:C(3), A(1), B(2)
        List<VideoInfo> originalList = Arrays.asList(
            new VideoInfo("视频C", 3),
            new VideoInfo("视频A", 1),
            new VideoInfo("视频B", 2)
        );
        
        // 排序后:A(1), B(2), C(3)
        List<VideoInfo> sortedList = new ArrayList<>(originalList);
        sortedList.sort(Comparator.comparingInt(VideoInfo::getWeight));
        
        System.out.println("=== 不排序的区间分配 ===");
        printIntervals(originalList);
        
        System.out.println("\n=== 排序后的区间分配 ===");
        printIntervals(sortedList);
        
        // 测试10000次,验证概率分布
        System.out.println("\n=== 概率分布测试(不排序,10000次)===");
        testProbability(originalList);
    }
    
    private static void printIntervals(List<VideoInfo> list) {
        int total = 0;
        int start = 0;
        
        for (VideoInfo video : list) {
            int end = start + video.getWeight();
            System.out.printf("%s(权重%d): [%d, %d)%n", 
                video.getName(), video.getWeight(), start, end);
            start = end;
        }
        System.out.println("总权重: " + start);
    }
    
    private static void testProbability(List<VideoInfo> list) {
        Map<Integer, List<VideoInfo>> weightMap = new HashMap<>();
        int totalWeight = 0;
        
        for (VideoInfo video : list) {
            totalWeight += video.getWeight();
            weightMap.computeIfAbsent(totalWeight, k -> new ArrayList<>()).add(video);
        }
        
        List<Integer> sortedKeys = new ArrayList<>(weightMap.keySet());
        Collections.sort(sortedKeys);
        
        Random random = new Random();
        Map<String, Integer> countMap = new HashMap<>();
        
        for (int i = 0; i < 10000; i++) {
            int randomNum = random.nextInt(totalWeight);
            int key = findKey(sortedKeys, randomNum);
            VideoInfo selected = weightMap.get(key).get(0);
            countMap.merge(selected.getName(), 1, Integer::sum);
        }
        
        countMap.forEach((name, count) -> 
            System.out.printf("%s: %d次 (%.2f%%)%n", 
                name, count, count / 100.0));
    }
    
    private static int findKey(List<Integer> sortedKeys, int randomNum) {
        for (int key : sortedKeys) {
            if (randomNum < key) {
                return key;
            }
        }
        return sortedKeys.get(sortedKeys.size() - 1);
    }
}

输出结果:

=== 不排序的区间分配 ===
视频C(权重3): [0, 3)
视频A(权重1): [3, 4)
视频B(权重2): [4, 6)
总权重: 6

=== 排序后的区间分配 ===
视频A(权重1): [0, 1)
视频B(权重2): [1, 3)
视频C(权重3): [3, 6)
总权重: 6

=== 概率分布测试(不排序,10000次)===
视频C: 5012次 (50.12%)
视频A: 1689次 (16.89%)
视频B: 3299次 (32.99%)

什么时候需要排序?

虽然算法本身不需要排序,但以下情况可能需要:

  1. 业务需求:比如权重相同时按某种规则优先

    // 如果权重相同,按创建时间排序
    list.sort((v1, v2) -> {
        if (v1.getWeight() != v2.getWeight()) {
            return Integer.compare(v1.getWeight(), v2.getWeight());
        }
        return v1.getCreateTime().compareTo(v2.getCreateTime());
    });
    
  2. 调试方便:排序后更容易理解和调试

    // 排序后,累积权重是递增的,更容易看懂
    // [1, 3, 6]  vs  [3, 4, 6]
    
  3. 缓存优化:如果同一个列表会被多次查询,排序后可以利用二分查找

    // 排序后可以用二分查找
    int index = Collections.binarySearch(sortedKeys, randomNum);
    if (index < 0) {
        index = -index - 1;
    }
    

结论

累积权重算法本身不要求输入列表有序,它只是按照你提供的顺序分配区间。顺序不影响概率的正确性,只会改变哪个随机数区间对应哪个元素。

所以你可以:

  • ✅ 保持原始顺序直接使用
  • ✅ 按任意业务规则排序后使用
  • ✅ 随机打乱后使用

概率分布始终正确,只要权重值不变!