kmp openharmony 滑动窗口峰值检测与区间最大值分析
本文介绍了一个基于Kotlin Multiplatform和OpenHarmony的滑动窗口峰值检测系统,用于分析时间序列数据中的短时异常峰值。该系统采用单调双端队列算法,在O(n)时间复杂度内计算每个窗口的最大值,并能自动识别全局峰值窗口。主要功能包括:1) 输入数值序列和窗口大小;2) 计算每个滑动窗口的最大值;3) 定位全局峰值窗口及其样本区间;4) 通过ArkTS界面可视化展示分析结果。该

在接口耗时、CPU 使用率、磁盘 I/O、QPS 等指标分析中,我们经常不只关心“整体平均水平”,而是更在意:
某一小段时间内有没有出现极端高值的短时峰值?
哪些窗口区间内的指标值“持续偏高”,值得重点告警或调优?
本案例基于 Kotlin Multiplatform(KMP)与 OpenHarmony,实现了一个滑动窗口峰值检测与区间最大值分析器:
- 输入一串数值序列和一个窗口大小
window; - 使用单调双端队列在 \( O(n) \) 时间内计算每个窗口的最大值;
- 自动找出全局峰值窗口,给出该窗口覆盖的样本区间与峰值大小;
- 通过 ArkTS 单页面板可视化展示所有窗口的最大值列表,辅助你做短时峰值诊断。
一、问题背景与典型场景
在 AIOps 与性能诊断场景中,常见需求包括:
-
接口耗时短时峰值分析
一次性能压测中,接口耗时序列整体还算平稳,但某一小段时间出现了明显抖升,需要快速定位这段时间对应的窗口区间。 -
CPU 使用率峰值段检测
系统大部分时间 CPU 只有 30% 左右,但偶尔会出现 3~5 秒的 90% 峰值,需要找到这些“峰值窗口”进行排查。 -
QPS / TPS 某段时间突刺
业务流量整体正常,但某几秒钟突然涌入流量洪峰,希望找出这些短时突刺区间,结合日志做关联分析。
这类问题常可以抽象为:
给定数值序列 \( a_0, a_1, \dots, a_{n-1} \) 和窗口大小 \( w \),
对于每个窗口 \( [i, i + w - 1] \) 计算其最大值 \( max_i \),
并找到所有 \( max_i \) 中的全局最大值及其对应窗口区间。
朴素做法是对每个窗口都重新扫描一次,时间复杂度为 \( O(nw) \),当样本很多、窗口也不小时,终端设备难以承受。
本案例采用单调队列实现,整体复杂度降低为 \( O(n) \),非常适合在 OpenHarmony 终端侧长时间运行。
二、Kotlin 滑动窗口分析引擎
1. 输入格式设计
为与前一个“有序阈值定位与二分查找分析”案例保持一致,本案例继续使用简洁的配置文本输入:
window=3
series=80,95,120,150,180,205,230,260
window:整数,表示滑动窗口大小(例如 3 表示每 3 个样本为一组);series:一串以,/ 空格 /;/ 换行分隔的数值序列。
解析时会忽略空白字符,并自动过滤非法数值。
2. Kotlin 分析主入口
在 App.kt 中,我们定义了对外暴露的分析函数,并通过 @JsExport 让 OpenHarmony 侧可以直接调用:
@JsExport
fun slidingWindowMaxAnalyzer(inputData: String): String {
val sanitized = inputData.trim()
if (sanitized.isEmpty()) {
return "❌ 输入为空,请按 window=3\\nseries=80,95,120,150,... 形式提供数据"
}
val lines = sanitized.lines()
.map { it.trim() }
.filter { it.isNotEmpty() }
var windowSize: Int? = null
var series: List<Double> = emptyList()
for (line in lines) {
when {
line.startsWith("window=", ignoreCase = true) -> {
windowSize = line.substringAfter("=").trim().toIntOrNull()
}
line.startsWith("series=", ignoreCase = true) -> {
series = line.substringAfter("=")
.split(",", " ", ";", "\n")
.mapNotNull { it.trim().takeIf { s -> s.isNotEmpty() }?.toDoubleOrNull() }
}
}
}
if (windowSize == null || windowSize!! <= 0) {
return "❌ 未找到合法的 window=... 配置,请提供大于 0 的窗口大小"
}
if (series.size < windowSize!!) {
return "❌ 样本点数量 (${series.size}) 小于窗口大小 ($windowSize),无法执行滑动窗口分析"
}
val w = windowSize!!
val (windowMax, peakIndex) = computeSlidingWindowMax(series, w)
val builder = StringBuilder()
builder.appendLine("📈 滑动窗口峰值检测与区间最大值分析报告")
builder.appendLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
builder.appendLine("样本数量: ${series.size}")
builder.appendLine("窗口大小: $w")
builder.appendLine("窗口数量: ${windowMax.size}")
builder.appendLine()
// 全局峰值窗口
val globalPeakValue = windowMax[peakIndex]
builder.appendLine("🔥 全局峰值窗口")
builder.appendLine("峰值窗口索引: $peakIndex (覆盖样本区间 [$peakIndex, ${peakIndex + w - 1}])")
builder.appendLine("窗口内最大值: $globalPeakValue")
builder.appendLine("窗口内样本: " +
series.subList(peakIndex, peakIndex + w).joinToString(prefix = \"[\", postfix = \"]\"))
builder.appendLine()
// 所有窗口最大值列表
builder.appendLine("🧮 所有窗口最大值列表")
windowMax.forEachIndexed { idx, value ->
builder.appendLine("窗口 $idx ([$idx, ${idx + w - 1}]): 最大值 = $value")
}
builder.appendLine()
builder.appendLine("🧠 工程化解读")
builder.appendLine("- 通过固定窗口滑动,可以快速观察短时间内的极端高值区间;")
builder.appendLine("- 使用双端队列实现的线性算法,时间复杂度为 O(n),适合在终端设备侧实时运行;")
builder.appendLine("- 可以与阈值分析、异常检测模块联动,对峰值窗口叠加更多上下文信息。")
return builder.toString().trim()
}
可以看到,整个函数的职责非常清晰:
- 负责解析
window和series; - 做必要的参数校验(窗口大小、样本数量);
- 调用核心算法
computeSlidingWindowMax拿到“每个窗口最大值 + 全局峰值窗口索引”; - 以多行文本的形式返回一份可以直接在 ArkTS
Text中展示的报告。
3. 单调队列实现滑动窗口最大值
核心算法实现如下:
private fun computeSlidingWindowMax(
series: List<Double>,
windowSize: Int
): Pair<List<Double>, Int> {
// 单调队列存储“可能成为当前/未来窗口最大值”的下标,队列中的值对应的样本单调递增索引,但值单调递减
val deque = ArrayDeque<Int>()
val windowMax = mutableListOf<Double>()
var globalPeakIndex = 0
fun pushIndex(i: Int) {
val current = series[i]
while (deque.isNotEmpty() && series[deque.last()] <= current) {
deque.removeLast()
}
deque.addLast(i)
}
// 初始化前 windowSize 个元素
for (i in 0 until windowSize) {
pushIndex(i)
}
windowMax += series[deque.first()]
// 滑动窗口
for (i in windowSize until series.size) {
// 移除已经滑出窗口左边界的下标
while (deque.isNotEmpty() && deque.first() <= i - windowSize) {
deque.removeFirst()
}
pushIndex(i)
windowMax += series[deque.first()]
}
// 找到全局峰值窗口索引
var maxValue = windowMax[0]
for (i in 1 until windowMax.size) {
val v = windowMax[i]
if (v > maxValue) {
maxValue = v
globalPeakIndex = i
}
}
return windowMax to globalPeakIndex
}
关键思想:
-
双端队列(Deque)中存的是“候选最大值的下标”:
- 队首永远是当前窗口的最大值下标;
- 新元素入队前,会把队尾所有小于等于它的元素下标全部弹出(它们已经不可能再成为任何后续窗口的最大值)。
-
窗口向右滑动时:
- 移除所有已经滑出左边界的下标(
<= i - windowSize); - 将新元素下标按单调队列规则入队;
- 队首对应的元素就是当前窗口的最大值。
- 移除所有已经滑出左边界的下标(
复杂度分析:
- 每个下标最多进队一次、出队一次,因此总操作次数为 \( O(n) \);
- 每次窗口滑动只涉及常数次队列操作;
- 算法整体时间复杂度 \( O(n) \),空间复杂度 \( O(n) \)(存储窗口最大值列表)。
这比朴素的 \( O(nw) \) 解法在大多数指标序列场景下要高效得多。
三、OpenHarmony 侧调用与 UI 展示思路
在 ArkTS 页面中,可以像之前的案例一样引入 Kotlin/JS 导出的函数:
import { slidingWindowMaxAnalyzer } from './hellokjs'
页面侧建议维护两个输入状态:
windowSize:窗口大小(可以通过数字输入框或滑块调整);seriesInput:样本数据输入,多行TextArea,支持80,95,120...或series=80,95,120...两种形式。
调用时拼接 payload:
const seriesLine = this.seriesInput.includes('series=') ? this.seriesInput : `series=${this.seriesInput}`
const payload = `window=${this.windowSize}\n${seriesLine}`
this.result = slidingWindowMaxAnalyzer(payload)
展示区域:
- 使用等宽字体显示 Kotlin 返回的多行分析报告;
- 可以对“🔥 全局峰值窗口”、“窗口 k ( [i, j] ) 最大值 = …”等行做简单的颜色高亮或分组排版。
四、工程化扩展与组合玩法
基于当前滑动窗口最大值分析能力,可以进一步做很多工程级玩法:
-
与阈值分析组合
在每个窗口最大值上再叠加阈值判断,标记“超过 SLO 阈值的窗口数量、比例、连续分布”等。 -
与异常检测/趋势分析联动
把“滑动窗口最大值序列”作为新的时间序列输入到异常检测或趋势分段算法中,从更高维度观察峰值模式。 -
多窗口对比
支持一次性输入多个窗口大小(例如 3/5/10),分别输出窗口最大值序列,帮助选择更合适的平滑窗口。 -
端侧实时监控
结合 KMP + OpenHarmony 的跨端能力,将这套滑动窗口分析逻辑下沉至设备端,在没有后端支撑的场景下做轻量级实时监控与预警。
通过这个滑动窗口峰值检测案例,你可以把“算法题里的经典单调队列技巧”直接落地到 AIOps、性能调优和终端监控场景中,
在资源有限的设备上依然保持足够高效、可解释的分析能力。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)