最近接了个统计需求,需要统计某一段时间内的最高在线人数
本来最高在线人数是前期最好处理,根据长连接建立和中断统计人数,将最高值入库。
但是前期因为某些原因不太好做,那就只能后期来做处理。
数据
我能拿到的数据包含进入和离开时间,统计也按照这两个时间来。
思路
最开始想的是将若干个时间段放在时间轴上,然后用类似取交集的方式计算最高在线人数。
看着很像是用滑动窗口解决,为此还去letcode上找有没有类似的题目。
但是没找到,不得不转变思路。
首先需要一根时间轴,这个用链表实现,将链表的每个节点认为是一个时间点,用index来表示。
那么进入和退出就是在指定的节点上插入数据;
最后再遍历这条链表,根据每个节点不同的状态执行 +1 -1 操作
但是上述方法会浪费大量的空间,因为可能存在某个时间点没有进入退出,但是链表上对应的节点依然存在
需要想办法把这些节点给干掉
优化
之所以要提前建立节点,是因为在插入数据的时候没法保证顺序,后面遍历的时候一定要保证链表节点是按照时间顺序排列的。
即便对原始数据进行排序,因为有两个时间的缘故,也没什么用处
尝试把进入和退出拆分到两条链表,再把原始数据按照两个时间分别排序。
然后先遍历进入,再遍历退出。没办法在一个循环里搞定。
再考虑怎么遍历。一条链表的情况下,只需要对每个节点做判断;但是两条链表的话,如何保证是在遍历每一秒,因为此时index不能代表时间点。
可以使用双指针。
第一个指针指向进入链表,以这条链表为准遍历。
遍历开始,每个指针都执行头节点
进入指针步进,同时做+1操作,根据当前节点的时间来判断退出链表是否步进,当进入时间晚于退出时间,则退出指针步进,此时方才做-1操作
进入链表结束时,退出链表肯定没有完,但是后面的数据只会有-1操作,不影响最高人数统计,可以不用处理
实际上的遍历操作就是一个双链表合并排序问题
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| private static class VisitNode {
private int count = 0;
private int second = 0;
private VisitNode next;
private Long time; }
private VisitNode count(long second, VisitNode tail, VisitNode head, LiveStat s, long time) { if (tail == head) { head.next = new VisitNode(); tail = head.next; tail.second = ((int) second); tail.count = 1; tail.time = time;
} else if (tail.second == second) { tail.count += 1; } else { tail.next = new VisitNode(); tail = tail.next; tail.second = ((int) second); tail.count = 1; tail.time = time; } return tail; }
VisitNode enterHead = new VisitNode(); VisitNode quietHead = new VisitNode(); VisitNode enterTail = enterHead, quietTail = quietHead;
List<LiveStat> enterStat; List<LiveStat> quietStat;
for (LiveStat liveStat : enterStat) { Long second = liveStat.getStartTime();
long se = (second - startTime) / 1000;
enterTail = count(se, enterTail, enterHead, liveStat, liveStat.getStartTime());
}
for (LiveStat liveStat : quietStat) { Long second = liveStat.getEndTime();
long se = (second - startTime) / 1000; quietTail = count(se, quietTail, quietHead, liveStat, liveStat.getEndTime());
}
int max = 0; int count = 0; quietHead = quietHead.next; enterHead = enterHead.next;
while (enterHead != null) { if (enterHead.second < quietHead.second) { logger.info("进入时间={}", DateUtils.getAllTime(enterHead.time)); count += enterHead.count; enterHead = enterHead.next; } else if (enterHead.second > quietHead.second) { logger.info("退出时间={}", DateUtils.getAllTime(enterHead.time)); count -= quietHead.count; quietHead = quietHead.next; } else { logger.info("同时时间={}", DateUtils.getAllTime(enterHead.time)); count -= quietHead.count; count += enterHead.count;
quietHead = quietHead.next; enterHead = enterHead.next; } if (count >= max) { max = count; }
}
|