当前位置: 首页 > ai >正文

实现游戏排行榜

实现一个游戏排行榜要求有:

  • 1.支持添加/更新玩家分数
  • 2.支持查询玩家排名
  • 3.支持获取前N名玩家
  • 4.需要考虑并发安全

代码实现:

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;public class GameRankSystem {private final ConcurrentHashMap<String, Long> hash = new ConcurrentHashMap<>();// 分数排名存储(分数 -> 玩家ID集合),使用线程安全的跳表实现,分数按降序排列private final ConcurrentSkipListMap<Long, Set<String>> rankList =new ConcurrentSkipListMap<>(Comparator.reverseOrder());public void addScore(String playerId,long score) {hash.compute(playerId, (id, oldScore) -> {if (oldScore != null) {// 如果已有分数,先从排名中移除旧分数removePlayerFromRankings(id, oldScore);}// 更新分数并添加到新排名addPlayerToRankings(id, score);return score;});}private void addPlayerToRankings(String playerId, long score) {rankList.compute(score,(oldScore,players)->{if(players==null) {players=ConcurrentHashMap.newKeySet();}players.add(playerId);return players;});}private void removePlayerFromRankings(String playerId, long score) {rankList.computeIfPresent(score,(s,players)->{hash.remove(playerId);return players.isEmpty()? null:players;});}public int getPlayerRank(String playerId) {Long score = hash.get(playerId);if(score==null) {return -1;}int rank=1;for(Map.Entry<Long, Set<String>> entry : rankList.headMap(score).entrySet()) {rank+=entry.getValue().size();}Set<String> sameScorePlayers = rankList.get(score);if (sameScorePlayers!=null) {for(String players:sameScorePlayers) {if(playerId.equals(players)) {break;}rank++;}}return rank;}public List<String> getTopN(int n ) {List<String> topN = new ArrayList<>();int count=0;for(Map.Entry<Long, Set<String>> entry : rankList.entrySet()) {for (String player : entry.getValue()) {if(count>=n) {break;}topN.add(player);count++;}//双层break跳出循环。if(count>=n) {break;}}return topN;}}

1.为什么选用这样的数据结构呢

在设计并发游戏排名系统时,我们需要考虑:

  • 1.高效的插入,更新,查询O(1)或0(log n)
  • 2.线程安全
  • 3.支持高效的范围查询(如获取前N名)
  • 4.处理同分玩家(多个玩家可能有相同分数)

1.ConcurrentHashMap存储玩家分数

作用:存储playerId -> score的映射

为什么选择它:

线程安全:ConcurrentHashMap是线程安全的哈希表,支持高并发读写

O(1)查询:可以快速获取某个玩家的分数。

原子性更新:使用compute()方法可以保证get+update操作的原子性

2.ConcurrentSkipListMap存储分数排名

作用:存储score -> Set<playerId> 的映射,并按分数降序排列。

为什么选择它?

线程安全:ConcurrentSkipListMap是并发版本的跳表(SkipList),支持高并发访问。

有序性:自动按分数进行排序(使用Comparator.reverseOrder()使分数降序排列)。

高效的范围查询:headMap可以快速获取所有比score高的分数(用于计算排名)

遍历时直接按分数从高到低获取前N名(getTopN只需O(N)时间)。

支持同分玩家:使用Set<String>存储相同的分数玩家,避免重复。

对比其他可能的数据结构:

方案1:TreeMap+HashMap(非线程安全)

问题:

TreeMap不是线程安全的,高并发环境下需要额外加锁,性能下降

HashMap也需要同步,否则可能导致数据不一致

方案2:PriorityQueue(堆结构)

问题:

标准PriorityQueue不是线程安全的

更新某个玩家的分数时,需要先删除再插入O(N)时间,效率低

无法高效的计算某个玩家的排名(堆结构不支持O(1)或O(log n)排名查询

方案3:数据库(如Redis ZSET)

优点:

Redis的ZSET天然支持排名,范围查询,分数更新

缺点:

需要引入外部存储,增加系统复杂度

如果仅需内存计算,ConcurrentSkipListMap是更轻量级的替代方案

总结:

当前方案的优缺点:

优点:

线程安全:ConcurrentHashMap+ConcurrentSkipListMap都是并发优化的数据结构,无需额外同步

高效查询:

获取玩家分数:O(1)(ConcurrentHashMap)

计算玩家排名:O(log n)跳表的查询范围。

获取前N名:O(n)(直接遍历跳表)

支持同分玩家:使用Set<String>存储相同的分数的玩家,避免冲突。

缺点:

内存占用较高:

ConcurrentSkipListMap相比TreeMap占用更多的内存(因为跳表的多层索引结构)。

同分玩家的排名顺序不固定:

如果多个玩家同分,getPlayerRank返回的排名可能因为并发插入顺序而变化(如需严格顺序,可改用List并加锁,但会影响性能)。

分析一段代码:

 public void addScore(String playerId,long score) {hash.compute(playerId, (id, oldScore) -> {if (oldScore != null) {// 如果已有分数,先从排名中移除旧分数removePlayerFromRankings(id, oldScore);}// 更新分数并添加到新排名addPlayerToRankings(id, score);return score;});}

在调用这一段代码过程中参数对应关系:

1.方法定义原型

V compute(K key, BiFunction<K,V,V> remappingFunction)
  • 1.读取旧值(oldScore)
  • 2.执行Lambda逻辑
  • 3.写入新值(score)
  • 整个过程对当前键playerId是原子的。

对比其他方法:

方法Lambda参数用途
compute()(K, V) -> V通用更新
computeIfAbsent()K -> V仅当键不存在时插入
computeIfPresent()(K, V) -> V仅当键存在时更新
merge()(V, V) -> V合并新旧值
http://www.xdnf.cn/news/16922.html

相关文章:

  • Spring Boot 的事务注解 @Transactional 失效的几种情况
  • 从马武寨穿越关山
  • K8S部署ELK(五):集成Kibana实现日志可视化
  • [硬件电路-144]:模拟电路 - 开关电源与线性稳压电源常见的性能指标对比
  • Android设备认证体系深度解析:GMS/CTS/GTS/VTS/STS核心差异与认证逻辑
  • 【连接器专题】连接器做为固定连接介质的三种分类
  • 问题集000
  • Go语言常量
  • CAP 理论笔记
  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第四天(DOM编程和AJAX异步交互)
  • Mysql深入学习:InnoDB执行引擎篇
  • K8S几种常见CNI深入比较
  • Vue+SpringBoot+langchain4j实战案例:实现AI消息问答 及 Markdown打字机渲染效果
  • C语言与数据结构:从基础到实战
  • 基于 Spring Boot + Vue 实现人脸采集功能全流程
  • 大模型智能体(Agent)技术全景:架构演进、协作范式与应用前沿
  • Selenium Web 自动化
  • 【AI论文】ScreenCoder:通过模块化多模态智能体推动前端自动化中的视觉到代码生成技术发展
  • 【Django】-9- 单元测试和集成测试(上)
  • 使用 Spring Initializr 生成项目结构:Java 开发效率提升指南
  • centos9 安装docker engine
  • react native中markdown添加数学公式的支持
  • 【大模型核心技术】Agent 理论与实战
  • 【项目日志|苍穹外卖】 Day1:项目环境搭建与架构设计
  • 【Excel】利用函数和Power Query进行数据分析
  • NX969NX972美光固态闪存NX975NX977
  • Java,八股,cv,算法——双非研0四修之路day24
  • javaweb开发之Servlet笔记
  • Android 优化 - 日志 Log
  • 【MySQL进阶】------MySQL程序