9.1.5 时间测量

原文链接: https://kotlinlang.org/docs/time-measurement.html

9.1.5 时间测量

Kotlin 标准库提供了各种工具,让你可以用不同的单位计算和测量时间。精确的时间测量对于以下活动很重要:

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

默认情况下,时间使用单调时间源来测量,但也可以配置其他时间源。更多信息请参阅创建时间源。

计算时长

为了表示一段时间,标准库提供了 Duration 类。Duration 可以用 DurationUnit 枚举类中的以下单位来表示:

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

Duration 可以是正值、负值、零、正无穷或负无穷。

创建时长

要创建 Duration,请使用 Int、Long 和 Double 类型上可用的扩展属性:nanoseconds、microseconds、milliseconds、seconds、minutes、hours 和 days。

提示: 天指的是 24 小时的时间段,并不是日历上的天数。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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() {
    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
}

你还可以对 Duration 对象执行基本算术运算:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import kotlin.time.*
import kotlin.time.Duration.Companion.seconds

fun main() {
    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
}

获取字符串表示

让 Duration 有字符串表示可能很有用,这样你就可以打印、序列化、传输或存储它。

要获取字符串表示,请使用 .toString() 函数。默认情况下,时间会按其中出现的每个单位进行报告。例如:1h 0m 45.677s 或 -(6d 5h 5m 28.284s)

要配置输出,请使用 .toString() 函数,并把所需的 DurationUnit 和小数位数作为函数参数传入:

1
2
3
4
5
6
7
8
9
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit

fun main() {
    // 以秒为单位打印,保留 2 位小数
    println(5887.milliseconds.toString(DurationUnit.SECONDS, 2))
    // 5.89s
}

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

1
2
3
4
5
import kotlin.time.Duration.Companion.seconds

fun main() {
    println(86420.seconds.toIsoString()) // PT24H0M20S
}

转换时长

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

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

例如:

1
2
3
4
5
6
7
8
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

fun main() {
    val thirtyMinutes: Duration = 30.minutes
    println(thirtyMinutes.inWholeSeconds)
    // 1800
}

或者,你可以在以下扩展函数中把所需的 DurationUnit 作为函数参数传入:

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

例如:

1
2
3
4
5
6
7
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit

fun main() {
    println(270.seconds.toDouble(DurationUnit.MINUTES))
    // 4.5
}

比较时长

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes

fun main() {
    val thirtyMinutes: Duration = 30.minutes
    val halfHour: Duration = 0.5.hours
    println(thirtyMinutes == halfHour)
    // true
}

要比较 Duration 对象,请使用比较运算符(<、>):

1
2
3
4
5
6
7
import kotlin.time.Duration.Companion.microseconds
import kotlin.time.Duration.Companion.nanoseconds

fun main() {
    println(3000.microseconds < 25000.nanoseconds)
    // false
}

把时长拆分为各个组成部分

要把 Duration 拆分为各个时间组成部分并执行进一步的操作,请使用 toComponents() 函数的这个重载。把你的操作作为函数或 lambda 表达式作为函数参数添加上去。

例如:

1
2
3
4
5
6
7
8
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

fun main() {
    val thirtyMinutes: Duration = 30.minutes
    println(thirtyMinutes.toComponents { hours, minutes, _, _ -> "${hours}h:${minutes}m" })
    // 0h:30m
}

在这个例子中,lambda 表达式把 hours 和 minutes 作为函数参数,并用下划线(_)表示未使用的 seconds 和 nanoseconds 参数。该表达式使用字符串模板返回一个拼接后的字符串,以获得 hours 和 minutes 所需的输出格式。

测量时间

为了跟踪时间的流逝,标准库提供了一些工具,让你可以轻松地:

  • 以所需的时间单位测量执行某段代码所花的时间。
  • 标记一个时间点。
  • 比较并相减两个时间点。
  • 检查自某个特定时间点以来过去了多少时间。
  • 检查当前时间是否已经过了某个特定时间点。

测量代码执行时间

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

1
2
3
4
5
6
7
8
import kotlin.time.measureTime

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

要测量执行一段代码所花的时间并返回该代码块的值,请使用内联函数 measureTimedValue。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import kotlin.time.measureTimedValue

fun main() {
    val (value, timeTaken) = measureTimedValue {
        Thread.sleep(100)
        42
    }
    println(value) // 42
    println(timeTaken) // 例如 103 ms
}

默认情况下,这两个函数都使用单调时间源。

标记时间点

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

1
2
3
4
5
6
import kotlin.time.*

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

测量时间差

要测量来自同一时间源的 TimeMark 对象之间的差异,请使用减法运算符(-)。

要比较来自同一时间源的 TimeMark 对象,请使用比较运算符(<、>)。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import kotlin.time.*

fun main() {
   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
}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import kotlin.time.*
import kotlin.time.Duration.Companion.seconds

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

   // 还没有过 5 秒
   println(mark2.hasPassedNow())
   // false

   // 等待六秒
   Thread.sleep(6000)
   println(mark2.hasPassedNow())
   // true

}

时间源

默认情况下,时间使用单调时间源来测量。单调时间源只会向前推进,不受时区变化等因素的影响。与单调时间相对的另一种方式是经过的真实时间,也称为挂钟时间。经过的真实时间是相对另一个时间点来测量的。

各平台的默认时间源

下表说明了各平台默认的单调时间源:

| 平台 | 来源 |

| Kotlin/JVM | System.nanoTime() | | Kotlin/JS(Node.js) | process.hrtime() | | Kotlin/JS(浏览器) | window.performance.now() 或 Date.now() | | Kotlin/Native | std::chrono::high_resolution_clock 或 std::chrono::steady_clock |

创建时间源

在某些情况下,你可能想使用不同的时间源。例如,在 Android 上,System.nanoTime() 只在设备处于活动状态时计时;当设备进入深度休眠时,它就会丢失时间。要在设备处于深度休眠时继续跟踪时间,你可以创建一个使用 SystemClock.elapsedRealtimeNanos() 的时间源:

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

然后你就可以用这个时间源进行时间测量:

1
2
3
4
5
6
fun main() {
    val elapsed: Duration = RealtimeMonotonicTimeSource.measureTime {
        Thread.sleep(100)
    }
    println(elapsed) // 例如 103 ms
}

关于 kotlin.time 包的更多信息,请参阅我们的标准库 API 参考。