1. 概要

このチュートリアルでは、Kotlinで経過時間を計算するためのいくつかの異なるアプローチに慣れます。

まず、最新のコンピューティングで時間を測定するさまざまな方法を定義することから始めます。 次に、Kotlinで期間を計算するためのいくつかの異なる方法を評価します。

2. 時計の定義

最新のコンピューティングでは、少なくとも2種類の時計があります。 そのような時計の1つは、時刻または壁掛け時計の時刻として知られています。 時刻時計は、カレンダーに従って実際の日付と時刻を報告します。

たとえば、 System.currentTimeMillis()は、エポック時間からの秒数を返します。 つまり、これはJavaの時刻です。

時刻時計は、常に前進すること、または合理的に前進することさえ保証されていません。 それは最初は直感に反するように聞こえるかもしれません。 ただし、このようなクロックは、NTPサーバーからのドリフト、NTPサーバーの設定ミス、突然のリセット、VMの一時停止、うるう秒など、さまざまな理由で不正確になる可能性があります。 この種の奇妙なため、マシンは時間の前後に突然ジャンプする可能性があります

したがって、これらのクロックは、経過時間または経過時間を測定するための適切なツールではありません。負の経過時間が発生する可能性があるためです

単調時計は、現代のコンピューターで利用できる2番目のタイプの時計です。 このような時計は通常、コンピューターが起動してからのナノ秒数またはその他の任意のイベントを測定しています。 このため、常に前進することが保証されており、その結果、持続時間を測定するのに適しています。 JavaのSystem.nanoTime()は単調な時計です。

このチュートリアルでは、これら2つのクロックのいずれかによって各ソリューションを分類します。

3. measureTimeMillis関数

経過時間をミリ秒単位で測定するには、 measureTimeMillis(block:()-> Unit)関数を使用できます。 私たちがしなければならないのは、コードのブロックを渡すことだけです。

val elapsed = measureTimeMillis {
    Thread.sleep(100)
    println("Measuring time via measureTimeMillis")
}

assertThat(elapsed).isGreaterThanOrEqualTo(100)

measureTimeMillis()関数は、渡されたラムダ式を実行し、経過時間をミリ秒単位で返します。 このアプローチでは、ラムダ自体は外部コンテキストに値を返すことができません。

measureTimeMillis()関数は、内部で System.currentTimeMillis()メソッドを使用して経過時間を計算していることに注意してください。 したがって、は時刻時計に基づいており、可能であっても期間の計算はお勧めしません!

4. measureNanoTime関数

経過時間をナノ秒単位で測定するには、 measureNanoTime(block :()-> Unit)関数を使用できます。

val elapsed = measureNanoTime {
    Thread.sleep(100)
    println("Measuring time via measureNanoTime")
}

assertThat(elapsed).isGreaterThanOrEqualTo(100 * 1_000_000)

各ミリ秒は1,000,000ナノ秒であるため、ここではその乗算を実行しています。 measureTimeMillis()と同様に、この関数は経過時間のみを返すため、ラムダ自体はそれを囲むスコープに何も返すことができません。

さらに、measureNanoTime()は内部でSystem.nanoTime()メソッドを使用しています。 したがって、それは単調な時計に基づいています

5. TimeSource API

Kotlin 1.3では、時間間隔を測定するためのTimeSourceAPIとDurationAPIが導入されました

たとえば、この新しいAPIを使用して経過時間を測定する方法は次のとおりです。

@Test
@ExperimentalTime
fun `Given a block of code, When using measureTime, Then reports the elapsed time as a Duration`() {
    val elapsed: Duration = measureTime {
        Thread.sleep(100)
        println("Measuring time via measureTime")
    }

    assertThat(elapsed).isGreaterThanOrEqualTo(100.milliseconds)
    assertThat(elapsed).isGreaterThanOrEqualTo(100.toDuration(DurationUnit.MILLISECONDS))
}

これはまだ実験的なAPIであるため、kotlin.time.ExperimentalTimeアノテーションを使用してオプトインする必要があります。 kotlin.time.measureTime(block :()-> Unit)関数は、コードのブロックをラムダ式として受け入れ、実行中の経過時間を計算します。

measureTimeMillis()および measureNanoTime()とは対照的に、この関数はプリミティブ番号の代わりにkotlin.time.Durationインスタンスを返します。 。 さらに、期間インスタンスを作成するために、次の2つのアプローチを使用しました。

  • すべての数値データ型のミリ秒拡張プロパティ。 ミリ秒に加えて、秒、マイクロ秒、などの時間単位ごとの拡張プロパティがあります。
  • Int.toDuration(kotlin.time.DurationUnit)拡張関数。受信側のIntDurationに変換します。

measureTime()は、 TimeSource.Monotonic の内部実装を使用しているため、単調なクロックに基づいています。 また、この関数は経過時間のみを返すため、ラムダ自体はそれを囲むコンテキストに何も返すことができません。

measureTime()関数とは対照的に、 measureTimedValue(block:()-> T)実験関数は、経過時間に加えて値を返すことができます。 :

val (value, elapsed) = measureTimedValue {
    Thread.sleep(100)
    42
}

assertThat(value).isEqualTo(42)
assertThat(elapsed).isGreaterThanOrEqualTo(100.milliseconds)

上に示したように、 measureTimedValue()関数は外部コンテキストに値を返します。 ここでは、破壊パターンを使用して、返された TimedValue インスタンスを(値、経過)ペアに分解しました。 もちろん、結果を破壊することなく、TimedValue自体を戻り型として使用することもできます。

val timedValue: TimedValue = measureTimedValue {
    Thread.sleep(100)
    42
}

assertThat(timedValue.value).isEqualTo(42)
assertThat(timedValue.duration).isGreaterThanOrEqualTo(100.milliseconds)

6. クラシックJava

Kotlinは、期間を測定するために、Javaのシステムユーティリティを介して多くの優れた簡潔な抽象化を提供します。 ただし、このタスクにプレーンJavaアプローチを使用することは引き続き可能です。

たとえば、 System.nanoTime()を使用して同じことを実現する方法は次のとおりです。

val start = System.nanoTime()
Thread.sleep(100)

assertThat(System.nanoTime() - start).isGreaterThanOrEqualTo(100 * 1_000_000)

System.currentTimeMillis()staticメソッドを使用して同じことを実現することもできます。 ただし、経過時間の計算に関しては、単調クロックの使用が常に推奨されるアプローチです

7. 結論

このチュートリアルでは、最近のほとんどのコンピューターで使用できるさまざまな種類の時計を見ました。 次に、現在の安定したAPI、実験的なAPI、昔ながらのJava APIなど、Kotlinでの経過時間を計算するさまざまな方法を評価しました。

いつものように、すべての例はGitHubから入手できます。