本文介绍如何准确统计日志中每个ip地址在240秒时间窗口内形成的连续会话序列数量,重点剖析常见逻辑错误(如误将同一ip的多个不相交序列合并计数),并提供健壮、可扩展的java实现方案。
本文介绍如何准确统计日志中每个ip地址在240秒时间窗口内形成的连续会话序列数量,重点剖析常见逻辑错误(如误将同一ip的多个不相交序列合并计数),并提供健壮、可扩展的java实现方案。
在分析 IPAddress:Timestamp 格式日志时,“会话序列”定义为:同一IP地址下,时间戳连续递增且任意相邻两条记录的时间差小于240秒;而当某条记录与其前一条记录的时间差 ≥240 秒时,即标志着当前序列结束、新序列开始。注意:这不是统计“长度≥2的子序列个数”,而是识别出所有极大连续时间窗口段——即无法再向左或向右扩展的、内部任意相邻时间差
原代码存在两个关键缺陷:
-
状态重用错误:使用单个 HashMap
记录每个IP的“上一次时间戳”,导致无法区分同一IP下的多个不相交序列。例如 1.2.3.4 在 t=0,50,60,70(窗口内)和 t=1500,1600(与前一段间隔 >240s)应视为两个独立序列,但原逻辑仅用一个 t.get(ip) 跟踪,使第二次序列的判定失去起点依据; - 触发条件错误:list.get(i).getTimestamp() - t.get(entry.getKey()) > FOUR_MINUTES 实际检查的是“当前行与‘上一序列起点’的差”,而非“当前行与前一行的差是否 ≥240”,逻辑与问题定义不符。
✅ 正确解法:对每个IP的有序时间戳列表(已按时间升序排列),采用滑动窗口 + 贪心分段策略:
- 遍历排序后的时间戳列表;
- 维护当前序列的起始时间 startTs;
- 若 currentTs - startTs >= 240,说明当前记录已超出以 startTs 为起点的窗口 → 当前序列结束,count++,并以 currentTs 作为新序列起点;
- 否则继续扩展当前序列。
以下是修正后的 Java 实现:
public int countSequences(Map<string list>> ipMap) {
final long FOUR_MINUTES = 240L;
int count = 0;
for (List<row> rows : ipMap.values()) {
if (rows.isEmpty()) continue;
// 确保按时间戳升序排列(关键!)
rows.sort(Comparator.comparingLong(Row::getTimestamp));
long startTs = rows.get(0).getTimestamp();
count++; // 至少存在一个序列(单条记录也算一个序列)
for (int i = 1; i = FOUR_MINUTES) {
startTs = currentTs;
count++;
}
}
}
return count;
}</row></string>
? 关键注意事项:
- ✅ 必须对每个IP对应的时间戳列表预先排序(原题未明确数据是否有序,但实际日志常无序);
- ✅ 单条记录也构成一个合法序列(符合“序列定义”:不存在违反条件的相邻对);
- ✅ 使用 >= 判断(非 >),严格遵循“差值
- ⚠️ 若需支持毫秒级精度或超大时间范围,建议 long 类型保持不变,避免整数溢出。
该算法时间复杂度为 O(N log N),主要开销在排序;空间复杂度 O(1)(除输入存储外)。经测试,对示例输入:
1.2.3.4:0 1.2.3.4:50 1.2.3.4:60 1.2.3.4:70 5.6.7.8:80 1.2.3.4:1500 1.2.3.4:1600
输出结果为 3 —— 分别对应 1.2.3.4 的 [0,50,60,70]、5.6.7.8 的 [80]、1.2.3.4 的 [1500,1600](注意:原问题描述答案为 2,但按严格定义,孤立的 5.6.7.8:80 本身即构成一个序列,故总数应为 3;若业务要求序列长度 ≥2,可在循环中追加 minLength 校验)。











