我的大刀早已饥渴难耐了 --蛮王
游戏有很多的排行榜,如战力排行榜,等级排行榜,伤害排行榜,帮派排行榜,爬塔排行榜等,游戏的排行榜系统功能不难,很多人都知道如何实现,但是,不知萌新是否知道游戏所有排行榜系统其实是可以抽象出来,做成一个排行榜模板工具的。
我们可以回忆总结一下游戏所有的排行榜功能,它们的核心属性有两个,一个是玩家的唯一id,一个是排名,因此可以把这两个属性封装成基类,其余的排行榜系统都可以由这个基类衍生而来,比如战力排行榜,则它可多加一个属性表示战力值的;等级排行榜,则它可多加一个属性表示等级的,还可加一个表经验值的属性,作为同等级的次级排序;伤害排行榜,则它可多加一个属性表示累积伤害值的。当我们对排行榜进行排序时,通过操作这个基类的排序对象与它的邻近对象进行compareTo对比,对比前提是这个基类的继承类都实现了Comparable接口,然后判断是大于还是小于从而决定该排序对象是否上升还是下浮,从而完成排序。就这样通过对基类的操作实现了所有的排行榜系统排序,而后增加其他的排行榜功能时,我们只需新建一个排序对象让它继承自这个排序基类就可以了,它的排序就可以不用管了,一劳永逸。
不建议将玩家名字或玩家头像等作为这个基类的属性,因为很多游戏支持玩家改名,如果玩家名字改了,则排行榜上可能显示是之前的名字了。名字和头像这些可以直接根据玩家id去实时取。
如果是帮派排行榜,这个唯一id,就是指帮派id了。
综上,不难推出基类的实现为:
BaseRankOrder.java
package cn.xiaosheng996.rank;
/**
* 排行榜基类
*/
public abstract class BaseRankOrder<T extends BaseRankOrder<T>> implements Comparable<T> {
protected final long rid;// 唯一标志,rid
protected int rank;// 排名
//其余的等级,头像,名字等都需实时拿的,否则这些信息变化时还需更新排行榜里相应信息,没必要
public BaseRankOrder(long rid) {
this.rid = rid;
}
/**获取唯一id*/
public long getRid(){
return rid;
}
/**获取排名*/
public int getRank(){
return rank;
}
/**设置排名*/
public void setRank(int rank){
this.rank = rank;
}
/**比较 强制要求一定要实现*/
public abstract int compareTo(T o);
}
接下来就是排行榜模板类的实现了,因为我们要为游戏里所有的排行榜功能统一做排序,所以这里用到了泛型实现;因为可能同时有多个线程提交和修改相应的排行榜,所以这里用到了同步容器和同步锁去实现排序;此外,如果对于一些数量巨大的排行榜而且名次还会下降上升浮动的,这里还支持按一定时间间隔去排序;其余的,对于一些数量不多的排序都可以实时去排,即将下面的checkAll属性设置为false即可。
Sorter.java
public class Sorter<T extends BaseRankOrder<T>> implements Runnable {
protected final String name; //排行榜名字
protected final int total; //总量
protected final int period; //刷新间隔,为0表示实时排
protected final boolean checkAll; //是否检查所有
protected ConcurrentHashMap<Long, T> commits; //
protected volatile Map<Long, T> orderMap; //
protected volatile List<T> orderList; //
protected static final Logger logger = Logger.getLogger(Sorter.class);
public ScheduledExecutorService scheduleder() {
return SingleInstanceHolder.scheduleder;
}
final private static class SingleInstanceHolder {
final private static ScheduledExecutorService scheduleder = Executors
.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("Sorter");
return t;
}
});
}
/**
* 实例化
* @param name 名称
* @param total 容量
* @param period 刷新时间间隔,0表示实时刷新
* @param checkAll 是否需要每次提交数据都做全盘检查
*/
public Sorter(String name, int total, int period, boolean checkAll) {
this.name = name;
this.total = total;
this.period = period;
this.checkAll = checkAll;
this.commits = new ConcurrentHashMap<>();
this.orderMap = new HashMap<>(total + 10);
this.orderList = new ArrayList<>(total + 10);
if (this.period > 0) {
scheduleder().scheduleAtFixedRate(this, this.period, this.period, TimeUnit.SECONDS);
}
logger.fatal("创建排行榜 : " + this.toString());
}
public String getName() {
return name;
}
public int getTotal() {
return total;
}
public int getPeriod() {
return period;
}
public boolean isCheckAll() {
return checkAll;
}
public Map<Long, T> getCommits() {
return commits;
}
/**
* 清除排行榜
*/
public void clear() {
synchronized (this) {
this.orderList.clear();
this.orderMap.clear();
}
}
/**
* 当前容量
*/
public int size() {
return orderList.size();
}
/**
* 安全备份有序数据
*/
public List<T> safedList() {
synchronized (this) {
return new ArrayList<T>(this.orderList);
}
}
/**
* 安全备份无序数据
*/
public Set<T> safedValues() {
synchronized (this) {
return new HashSet<T>(this.orderMap.values());
}
}
public Set<Long> safedKeys() {
synchronized (this) {
return new HashSet<Long>(this.orderMap.keySet());
}
}
/** 获取前top名 */
public List<T> topList(int top) {
return subList(0, top);
}
/**
* 获取一个排名区间的成员
* @param begin
* @param end
*/
public List<T> subList(int begin, int end) {
final List<T> safedList = safedList();
if (safedList.isEmpty()) {
return new ArrayList<>();
}
if (begin < 0 || begin >= end || begin >= safedList.size()) {
logger.error("获取排行榜[" + name + "]从[" + begin + "]到[" + end + "]的数据,size=" + size());
return new ArrayList<>();
}
try {
int e = end > safedList.size() ? safedList.size() : end;
return (List<T>) safedList.subList(begin, e);
} catch (Exception e) {
logger.error("获取排行榜[" + name + "]从[" + begin + "]到[" + end + "]的数据,size=" + size(), e);
return new ArrayList<>();
}
}
/**
* 每页num个可以分多少页
* @param num 每页成员数
*/
public int pages(int num) {
final int length = size();
if (length == 0) {
return 0;
}
if (length <= num) {
return 1;
}
return (length % num == 0) ? (length / num) : ((length / num) + 1);
}
/**
* 按页显示
* @param page 第几页
* @param num 每页显示数量
*/
public List<T> pageList(int page, int num) {
final int pages = pages(num);
if (page > pages || page < 1) {
return new ArrayList<>();
}
final List<T> orderList = safedList();
final List<T> resultList = new ArrayList<>(num);
final int pageIndex = page - 1;
for (int i = pageIndex * num; i < pageIndex * num + num; i++) {
if (i >= orderList.size()) {
break;
}
resultList.add(orderList.get(i));
}
return resultList;
}
/**
* 获取成员
* @param id
*/
public T getOrder(long rid) {
return (T) orderMap.get(rid);
}
/**
* 获取成员
* @param rank 排名
*/
public T getOrder(int rank) {
if (rank < 1 || rank > orderList.size()) {
return null;
}
return (T) orderList.get(rank - 1);
}
/**
* 获取排名
*
* @param id
* @return
*/
public Integer getRank(long id) {
T order = getOrder(id);
return order == null ? null : order.getRank();
}
/**
* 提交成绩
* @param order
*/
public void commit(T order) {
if (this.period == 0) {
synchronized (this) {
this.submit(order);
}
} else {
this.commits.put(order.getRid(), order);
}
}
/**
* 定时刷新执行批量提交数据操作
*/
@Override
public void run() {
List<T> taskList = null;
synchronized (this) {
taskList = new ArrayList<>(this.commits.values());
this.commits = new ConcurrentHashMap<>();
}
if (!taskList.isEmpty()) {
taskList.forEach(e -> {
this.submit(e);
});
}
}
private int submit(T order) {
return pushSort(order);
}
private int pushSort(T order) {
try {
if (!orderMap.containsKey(order.getRid())) {
addLast(order);
return pushUp(order);
} else {
T old = orderMap.get(order.getRid());
if (checkAll) {// 全盘检查并且成绩下降
removal(order.getRid());
addLast(order);
return pushUp(order);
} else {//适合于只会递增的
int oldRank = old.getRank();
old = order;
old.setRank(oldRank);
return pushUp(old);
}
}
} finally {
reset();
}
}
private void addLast(T order) {
synchronized (this) {
orderMap.put(order.getRid(), order);
orderList.add(order);
order.setRank(orderList.size());
}
}
private void reset() {
synchronized (this) {
for (int i = orderList.size(); --i >= total;) {
T rm = orderList.remove(i);
if (rm != null) {
orderMap.remove(rm.getRid());
}
}
}
}
/**
* 从列表删除一条记录并且重新设置名次
* @param id
*/
public void removal(Long rid) {
synchronized (this) {
for (int i = orderList.size(); --i >= 0;) {
if (orderList.get(i).getRid() == rid) {
orderList.remove(i);
orderMap.remove(rid);
break;
}
}
for (int i = 0; i < orderList.size(); i++) {
orderList.get(i).setRank(i + 1);
}
}
}
/**
* 向上推
* @param order
* @return
*/
private int pushUp(T order) {
int count = 0;
int beforeIndex = order.getRank() - 2;
for (int i = beforeIndex; i >= 0; i--) {
T before = (T) orderList.get(i);
int beforeRank = before.getRank();
if (order.compareTo(before) >= 0) {
break;
}
order.setRank(beforeRank);
before.setRank(beforeRank + 1);
orderList.set(i, order);
orderList.set(i + 1, before);
count++;
}
return count;
}
/**
* 向下推
* @param order
* @return
*/
private int pushDown(T order) {
int count = 0;
int index = order.getRank();// 下一个
for (int i = index; i < orderList.size(); i++) {
T next = (T) orderList.get(i);
int nextRank = next.getRank();
if (next.compareTo(order) >= 0) {// 比自己小,跳出
break;
}
// 比自己大,换位
order.setRank(nextRank);
next.setRank(nextRank - 1);
orderList.set(i - 1, next);
orderList.set(i, order);
count++;
}
return count;
}
/**
* 强制把所有数据立刻进行排序
*/
public void flush() {
this.run();
}
@Override
public String toString() {
return "Sorter[名称:" + name + ",最大容量:" + total + ",刷新时间:" + period + ",是否检查成绩下降:" + checkAll + "]";
}
}
这样,就可以为所有的排行系统排序了,接下来可以做个排行榜管理类,用于统一创建和管理这些排行榜系统,实现如下:
SorterManager.java
/**
* 排行榜管理器
*/
public class SorterManager {
/**
* 排行榜容器
*/
@SuppressWarnings("rawtypes")
public static final Map<String, Sorter> ALLSORTERS = new ConcurrentHashMap<>();
/**
* 获取不存在就创建排行榜
*
* @param name 名称
* @param total 总容量,不限量可写个尽量大的值
* @param period 刷新时间-秒0表示实时刷新
* @param checkAll 是否需要全部检查一次:对于成员的成绩会下降导致排名大幅波动就需要设置为true,每次提交成绩都从最底下开始往上检查
* @return
*/
@SuppressWarnings("unchecked")
public static <T extends BaseRankOrder<T>> Sorter<T> getOrCreateSorter(String name, int total, int period,
boolean checkAll) {
if (!ALLSORTERS.containsKey(name)) {
ALLSORTERS.put(name, new Sorter<T>(name, total, period, checkAll));
}
return ALLSORTERS.get(name);
}
/**
* 获取不存在就创建排行榜.默认最多存200个成员,实时刷新,做整体检查
* @param name 名称
* @return
*/
public static <T extends BaseRankOrder<T>> Sorter<T> getOrCreateSorter(String name) {
return getOrCreateSorter(name, 200, 0, false);
}
/**
* 获取不存在就创建排行榜.默认最多存100个成员,实时刷新,做整体检查
* @param name 名称
* @param total 容量
* @return
*/
public static <T extends BaseRankOrder<T>> Sorter<T> getOrCreateSorter(String name, int total) {
return getOrCreateSorter(name, total, 0, false);
}
}
至此,这个排行榜工具就实现了,举个使用栗子:
比如,我们要实现一个副本排行榜,达到最大关卡排序,如果相同关卡则按谁先到达时间排序,我们可以这样添加一个排序基类的实现类:
public class CopyRank extends BaseRankOrder<CopyRank> {
protected int copyId;
protected int reachTime;
public CopyRank(long rid, int copyId, int reachTime) {
super(rid);
this.copyId = copyId;
this.reachTime = reachTime;
}
@Override
public int compareTo(CopyRank o) {
if (this.copyId == o.copyId){
return this.reachTime - o.reachTime;
}else {
return o.copyId - this.copyId;
}
}
public int getCopyId() {
return copyId;
}
public void setCopyId(int copyId) {
this.copyId = copyId;
}
public int getReachTime() {
return reachTime;
}
public void setReachTime(int reachTime) {
this.reachTime = reachTime;
}
}
就写这么点就差不多了,一天写30个排行榜功能也不在话下,我们可以测试一番:
public static void main(String[] args){
//副本排行榜
Sorter<CopyRank> sequencer = SorterManager.getOrCreateSequencer("CopyRank");
for (int i = 1; i <= 500; i++) {
long rid = Probability.rand(1, 300); //比如有300个玩家
CopyRank rank = sequencer.getOrder(rid);
if (rank == null){
int initCopyId = Probability.rand(1, 5);
int initTime = Probability.rand(1, 100);
rank = new CopyRank(rid, initCopyId, initTime);
} else{
int newCopyId = rank.getCopyId() + Probability.rand(1, 5);
int newTime = rank.getReachTime() + Probability.rand(1, 5);
rank.setCopyId(newCopyId);
rank.setReachTime(newTime);
}
sequencer.commit(rank);
}
sequencer.safedList().forEach(e -> {
System.out.println("rid:" + e.getRid() + ", rank:" + e.getRank() + ", maxCopyId:" + e.getCopyId() + ",reachTime:" + e.getReachTime());
});
}
结果输出如下:
rid:96, rank:1, maxCopyId:27,reachTime:87
rid:232, rank:2, maxCopyId:20,reachTime:35
rid:196, rank:3, maxCopyId:20,reachTime:84
rid:88, rank:4, maxCopyId:19,reachTime:79
rid:168, rank:5, maxCopyId:18,reachTime:12
rid:143, rank:6, maxCopyId:17,reachTime:95
rid:35, rank:7, maxCopyId:16,reachTime:48
rid:191, rank:8, maxCopyId:14,reachTime:13
rid:21, rank:9, maxCopyId:14,reachTime:16
rid:101, rank:10, maxCopyId:14,reachTime:81
rid:192, rank:11, maxCopyId:13,reachTime:37
rid:27, rank:12, maxCopyId:13,reachTime:55
rid:80, rank:13, maxCopyId:13,reachTime:104
rid:161, rank:14, maxCopyId:12,reachTime:11
rid:116, rank:15, maxCopyId:12,reachTime:15
rid:62, rank:16, maxCopyId:12,reachTime:28
rid:158, rank:17, maxCopyId:12,reachTime:29
rid:165, rank:18, maxCopyId:12,reachTime:68
rid:2, rank:19, maxCopyId:12,reachTime:83
rid:300, rank:20, maxCopyId:12,reachTime:84
rid:299, rank:21, maxCopyId:12,reachTime:84
rid:279, rank:22, maxCopyId:12,reachTime:94
rid:230, rank:23, maxCopyId:11,reachTime:13
rid:76, rank:24, maxCopyId:11,reachTime:25
rid:87, rank:25, maxCopyId:11,reachTime:40
rid:54, rank:26, maxCopyId:11,reachTime:45
rid:228, rank:27, maxCopyId:11,reachTime:62
rid:10, rank:28, maxCopyId:11,reachTime:82
rid:202, rank:29, maxCopyId:11,reachTime:101
rid:118, rank:30, maxCopyId:10,reachTime:16
……
妥妥地ok了......
注意,这里只是实现,相应的排行榜初始化还需读者自己完成,它可以在服务器启动时从数据库加载初始化,类似如下:
private void initCopyRank() { String sql = null; try { //后面加数量,以时间换空间,避免开服时占用太多内存,给copyId建索引,提高检索效率 sql = "select t.rid, t.copyId, t.time from copy t order by t.copyId desc limit 50"; List<Map<String, Object>> rankList = getDB().query(sql); for (Map<String, Object> e : rankList) { long rid = (long) e.get("rid"); int maxCopyId = (int) e.get("copyId"); int time = (int) e.get("time"); CopyRank order = new CopyRank(rid, maxCopyId, time); copySorter.commit(order); } } catch (Exception e) { logger.error("初始化副本排行榜出错!", e); } }
网友评论