You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在NSTextView日志中查找最接近指定时间戳并滚动定位

实现NSTextView中定位最接近指定时间戳的日志位置

针对你这个15MB+日志的NSTextView场景,要实现「输入任意时间戳(即使不存在),定位到最接近的日志行」,咱们可以按以下步骤来搞:

1. 先把日志里所有时间戳和它们的位置捞出来

首先得用正则把所有符合yyyy-MM-dd HH:mm:ss格式的时间戳都匹配出来,同时记录它们在文本中的位置(NSRange),还要转成Date对象方便后续比较。这里要注意大文本的性能,别用一次性匹配所有结果的方法,用枚举遍历更高效:

// 重用DateFormatter,避免重复创建影响性能
private let timestampFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.timeZone = TimeZone.current // 或者根据你的日志时区调整
    return formatter
}()

private func extractAllTimestamps(from textView: NSTextView) -> [(date: Date, range: NSRange)] {
    guard let text = textView.string else { return [] }
    let regexPattern = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"
    guard let regex = try? NSRegularExpression(pattern: regexPattern) else { return [] }
    
    var timestamps: [(Date, NSRange)] = []
    let fullRange = NSRange(text.startIndex..., in: text)
    
    // 枚举所有匹配结果,适合大文本
    regex.enumerateMatches(in: text, options: [], range: fullRange) { match, _, _ in
        guard let match = match else { return }
        let matchRange = match.range
        let timestampString = (text as NSString).substring(with: matchRange)
        if let date = timestampFormatter.date(from: timestampString) {
            timestamps.append((date, matchRange))
        }
    }
    
    return timestamps
}

2. 把目标时间戳转成Date

用户从下拉菜单选的是字符串,先转成Date对象,这样才能和日志里的时间戳做时间差比较:

func convertTargetTimestamp(toDate timestampString: String) -> Date? {
    return timestampFormatter.date(from: timestampString)
}

3. 找到最接近的时间戳

遍历所有提取出来的时间戳,计算每个和目标时间的时间差绝对值,找出差值最小的那个。如果有两个时间差一样(比如目标时间刚好在两个日志时间中间),你可以选择定位到前一个或者后一个,这里示例选前一个:

func findClosestTimestamp(targetDate: Date, from timestamps: [(Date, NSRange)]) -> (Date, NSRange)? {
    guard !timestamps.isEmpty else { return nil }
    
    var closest: (Date, NSRange) = timestamps[0]
    var smallestDifference = abs(targetDate.timeIntervalSince(closest.date))
    
    for (date, range) in timestamps {
        let difference = abs(targetDate.timeIntervalSince(date))
        if difference < smallestDifference {
            smallestDifference = difference
            closest = (date, range)
        } else if difference == smallestDifference {
            // 可选逻辑:如果差值相同,选择更早的时间戳,或者更晚的
            // 这里保持原来的closest(更早的),如果要选更晚的就替换
            // closest = (date, range)
        }
    }
    
    return closest
}

4. 滚动NSTextView到目标位置

拿到最接近的时间戳的NSRange后,直接调用NSTextView的滚动方法,要是想平滑滚动,可以用NSAnimationContext做动画:

func scrollToClosestTimestamp(in textView: NSTextView, targetTimestampString: String) {
    let timestamps = extractAllTimestamps(from: textView)
    guard let targetDate = convertTargetTimestamp(toDate: targetTimestampString),
          let closest = findClosestTimestamp(targetDate: targetDate, from: timestamps) else {
        // 处理没有找到任何时间戳的情况
        return
    }
    
    // 直接滚动
    textView.scrollRangeToVisible(closest.range)
    
    // 可选:平滑滚动动画
    NSAnimationContext.runAnimationGroup({ context in
        context.duration = 0.3
        textView.animator().scrollRangeToVisible(closest.range)
    }, completionHandler: nil)
    
    // 可选:高亮这个时间戳
    textView.setSelectedRange(closest.range)
}

一些优化点

  • 因为日志很大(15MB+),不要每次查找都重新提取所有时间戳,可以在日志加载完成后或者日志更新时,缓存提取出来的时间戳数组,避免重复计算浪费性能。
  • 如果日志是实时更新的,要记得在新日志追加后,更新缓存的时间戳数组,比如监听NSTextView的didChangeNotification事件,追加新匹配的时间戳。
  • DateFormatter的时区要和日志里的时间戳时区一致,不然会出现时间差计算错误的问题。

内容的提问来源于stack exchange,提问作者gooberboobbutt

火山引擎 最新活动