Edit Page

时间测量

最终更新: 2024/03/21

Kotlin 标准库为你提供了一些工具, 使用不同的单位计算和测量时间. 精确的时间测量对下面这些活动是非常重要的:

  • 管理线程或进程
  • 收集统计数据
  • 检测超时
  • 调试

默认情况下, 会使用一个单调时间源(monotonic time source)测量时间, 但也可以配置使用其他时间源. 详情请参见, 创建时间源.

计算持续时间

为了代表一段时间, 标注库提供了 Duration 类. 一个 Duration 可以使用 DurationUnit 枚举类中的下面这些单位来表达:

  • NANOSECONDS
  • MICROSECONDS
  • MILLISECONDS
  • SECONDS
  • MINUTES
  • HOURS
  • DAYS

一个 Duration 可以是正值, 负值, 0, 正无穷, 或负无穷.

创建持续时间

要创建一个 Duration, 请使用 Int, Long, 和 Double 类型的 扩展属性 : nanoseconds, microseconds, milliseconds, seconds, minutes, hours, 和 days.

天表示24小时的时间长度. 不是日历上的天.

示例:

import kotlin.time.*
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.days

fun main() {
//sampleStart
    val fiveHundredMilliseconds: Duration = 500.milliseconds
    val zeroSeconds: Duration = 0.seconds
    val tenMinutes: Duration = 10.minutes
    val negativeNanosecond: Duration = (-1).nanoseconds
    val infiniteDays: Duration = Double.POSITIVE_INFINITY.days
    val negativeInfiniteDays: Duration = Double.NEGATIVE_INFINITY.days

    println(fiveHundredMilliseconds) // 输出结果为 500ms
    println(zeroSeconds)             // 输出结果为 0s
    println(tenMinutes)              // 输出结果为 10m
    println(negativeNanosecond)      // 输出结果为 -1ns
    println(infiniteDays)            // 输出结果为 Infinity
    println(negativeInfiniteDays)    // 输出结果为 -Infinity
//sampleEnd
}

你也可以对 Duration 对象进行基本的算数运算:

import kotlin.time.*
import kotlin.time.Duration.Companion.seconds

fun main() {
//sampleStart
    val fiveSeconds: Duration = 5.seconds
    val thirtySeconds: Duration = 30.seconds

    println(fiveSeconds + thirtySeconds)
    // 输出结果为 35s
    println(thirtySeconds - fiveSeconds)
    // 输出结果为 25s
    println(fiveSeconds * 2)
    // 输出结果为 10s
    println(thirtySeconds / 2)
    // 输出结果为 15s
    println(thirtySeconds / fiveSeconds)
    // 输出结果为 6.0
    println(-thirtySeconds)
    // 输出结果为 -30s
    println((-thirtySeconds).absoluteValue)
    // 输出结果为 30s
//sampleEnd
}

获取字符串表达

得到 Duration 的字符串表达形式会非常有用, 你可以用来打印, 序列化, 传输, 或保存.

要得到字符串表达, 请使用 .toString() 函数. 默认情况下, 会使用存在的每个单位来报告时间. 例如: 1h 0m 45.677s-(6d 5h 5m 28.284s)

要配置输出, 请使用 .toString() 函数, 以你希望的 DurationUnit 和小数位数, 作为函数参数:

import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit

fun main() {
//sampleStart
    // 使用秒单位, 2 位小数
    println(5887.milliseconds.toString(DurationUnit.SECONDS, 2))
    // 输出结果为 5.89s
//sampleEnd
}

要得到 ISO-8601 兼容 的字符串, 请使用 toIsoString() 函数:

import kotlin.time.Duration.Companion.seconds

fun main() {
//sampleStart
    println(86420.seconds.toIsoString()) // 输出结果为 PT24H0M20S
//sampleEnd
}

转换持续时间

要把你的 Duration 转换为不同的 DurationUnit, 请使用以下属性:

  • inWholeNanoseconds
  • inWholeMicroseconds
  • inWholeSeconds
  • inWholeMinutes
  • inWholeHours
  • inWholeDays

示例:

import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

fun main() {
//sampleStart
    val thirtyMinutes: Duration = 30.minutes
    println(thirtyMinutes.inWholeSeconds)
    // 输出结果为 1800
//sampleEnd
}

或者, 你也可以使用下面的扩展函数, 以你希望的 DurationUnit 作为函数参数:

  • .toInt()
  • .toDouble()
  • .toLong()

示例:

import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit

fun main() {
//sampleStart
    println(270.seconds.toDouble(DurationUnit.MINUTES))
    // 输出结果为 4.5
//sampleEnd
}

比较持续时间

要检查 Duration 对象是否相等, 请使用相等操作符 (==):

import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes

fun main() {
//sampleStart
    val thirtyMinutes: Duration = 30.minutes
    val halfHour: Duration = 0.5.hours
    println(thirtyMinutes == halfHour)
    // 输出结果为 true
//sampleEnd
}

要比较 Duration 对象, 请使用比较操作符 (<, >):

import kotlin.time.Duration.Companion.microseconds
import kotlin.time.Duration.Companion.nanoseconds

fun main() {
//sampleStart
    println(3000.microseconds < 25000.nanoseconds)
    // 输出结果为 false
//sampleEnd
}

将持续时间分解为不同的部分

要将一个 Duration 分解为不同的时间组成部分, 并进行后续的操作, 请使用 toComponents() 函数的重载版本. 将你希望执行的后续操作, 以函数或 Lambda 表达式的形式, 作为 toComponents() 函数的参数.

For example:

import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

fun main() {
//sampleStart
    val thirtyMinutes: Duration = 30.minutes
    println(thirtyMinutes.toComponents { hours, minutes, _, _ -> "${hours}h:${minutes}m" })
    // 输出结果为 0h:30m
//sampleEnd
}

在上面的示例中, Lambda 表达式使用 hoursminutes 作为参数, 另外还有下划线 (_) 对用于未使用的参数 secondsnanoseconds. Lambda 表达式使用 字符串模板, 得到所需要的 hoursminutes 的输出格式, 最后返回拼接的字符串.

测量时间

为了跟踪时间的流逝, 标准库提供了工具, 以便你可以轻松的完成以下任务:

  • 使用你希望的时间单位, 测量执行某些代码所需的时间.
  • 标记一个时刻.
  • 比较两个时刻, 并计算它们之间的差异.
  • 检查从某个特定的时刻开始, 经过了多少时间.
  • 检查当前时间是否已经经过了某个指定的时刻.

测量代码的执行时间

要测量执行一段代码消耗的时间, 请使用内联函数 measureTime:

import kotlin.time.measureTime

fun main() {
//sampleStart
    val timeTaken = measureTime {
        Thread.sleep(100)
    }
    println(timeTaken) // 例如 103 ms
//sampleEnd
}

要测量执行一段代码消耗的时间, 并且 返回这段代码的执行结果, 请使用内联函数 measureTimedValue.

示例:

import kotlin.time.measureTimedValue

fun main() {
//sampleStart
    val (value, timeTaken) = measureTimedValue {
        Thread.sleep(100)
        42
    }
    println(value)     // 输出结果为 42
    println(timeTaken) // 例如 103 ms
//sampleEnd
}

默认情况下, 这两个函数使用一个单调时间源(monotonic time source).

标记一个时刻

要标记一个特定的时刻, 请使用 TimeSource 接口, 和 markNow() 函数 来创建一个 TimeMark:

import kotlin.time.*

fun main() {
    val timeSource = TimeSource.Monotonic
    val mark = timeSource.markNow()
}

测量时刻之间的差异

要测量来自同一个时间源的 TimeMarks 对象之间的差异, 请使用减法操作符 (-).

要比较来自同一个时间源的 TimeMark 对象, 请使用比较操作符 (<, >).

示例:

import kotlin.time.*

fun main() {
//sampleStart
   val timeSource = TimeSource.Monotonic
   val mark1 = timeSource.markNow()
   Thread.sleep(500) // 睡眠 0.5 秒.
   val mark2 = timeSource.markNow()

   repeat(4) { n ->
       val mark3 = timeSource.markNow()
       val elapsed1 = mark3 - mark1
       val elapsed2 = mark3 - mark2

       println("Measurement 1.${n + 1}: elapsed1=$elapsed1, elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
   }
   
   println(mark2 > mark1) // 比较结果为 true, 因为 mark2 是在 mark1 之后捕获的.
   // 输出结果为 true
//sampleEnd
}

要检查是否已经经过了某个截止时刻, 或者是否已经到达超时时间, 请使用 hasPassedNow()hasNotPassedNow() 扩展函数:

import kotlin.time.*
import kotlin.time.Duration.Companion.seconds

fun main() {
//sampleStart
   val timeSource = TimeSource.Monotonic
   val mark1 = timeSource.markNow()
   val fiveSeconds: Duration = 5.seconds
   val mark2 = mark1 + fiveSeconds

   // 还没有经过 5 秒
   println(mark2.hasPassedNow())
   // 输出结果为 false
  
   // 等待 6 秒
   Thread.sleep(6000)
   println(mark2.hasPassedNow())
   // 输出结果为 true

//sampleEnd
}

时间源

默认情况下, 会使用一个单调时间源(monotonic time source)测量时间. 单调时间源只会向前移动, 不会受系统变化的影响, 比如时区变化. 单调时间的替代方案是流逝的真实时间(elapsed real time), 也叫做挂钟时间(wall-clock time). 流逝的真实时间是相对于另一个时间点来测量的.

各个平台的默认时间源

下表是各个平台的默认单调时间源:

平台 时间源
Kotlin/JVM System.nanoTime()
Kotlin/JS (Node.js) process.hrtime()
Kotlin/JS (browser) window.performance.now()Date.now()
Kotlin/Native std::chrono::high_resolution_clock or std::chrono::steady_clock

创建时间源

有些情况下, 你可能想要使用不同的时间源. 例如, 在 Android 中, System.nanoTime() 在设备活动时才计算时间. 当设备进入深度睡眠时, 它会失去对时间的追踪. 想要在设备深度睡眠时继续追踪时间, 你可以创建一个使用 SystemClock.elapsedRealtimeNanos() 的时间源:

object RealtimeMonotonicTimeSource : AbstractLongTimeSource(DurationUnit.NANOSECONDS) {
    override fun read(): Long = SystemClock.elapsedRealtimeNanos()
}

然后你就可以使用你的时间源来进行时间测量:

fun main() {
    val elapsed: Duration = RealtimeMonotonicTimeSource.measureTime {
        Thread.sleep(100)
    }
    println(elapsed) // 例如 103 ms
}

关于 kotlin.time 包, 更多详情请参见我们的 标准库 API 参考文档.