1. Обзор
В этой статье мы рассмотрим, как измерить прошедшее время в Java. Хотя это может показаться простым, есть несколько подводных камней, о которых мы должны знать.
Мы рассмотрим стандартные классы Java и внешние пакеты, предоставляющие функции для измерения прошедшего времени.
2. Простые измерения
2.1. currentTimeMillis()
Когда мы сталкиваемся с требованием измерить прошедшее время в Java, мы можем попытаться сделать это следующим образом:
long start = System.currentTimeMillis();
// ...
long finish = System.currentTimeMillis();
long timeElapsed = finish - start;
Если мы посмотрим на код, он имеет смысл. Мы получаем временную метку в начале и другую временную метку, когда код завершается. Прошедшее время представляет собой разницу между этими двумя значениями.
Однако результат может и будет неточным, поскольку System.currentTimeMillis()
измеряет время настенных часов
. Время настенных часов может измениться по многим причинам, например, изменение системного времени может повлиять на результаты, или дополнительная секунда испортит результат.
2.2. наноВремя()
Другой метод в классе java.lang.System — это
nanoTime()
. Если мы посмотрим на документацию по Java , мы найдем следующее утверждение:
«Этот метод можно использовать только для измерения прошедшего времени и он не связан ни с каким другим понятием системного или настенного времени».
Давайте использовать это:
long start = System.nanoTime();
// ...
long finish = System.nanoTime();
long timeElapsed = finish - start;
Код в основном такой же, как и раньше. Единственное отличие заключается в методе, используемом для получения временных меток — nanoTime()
вместо currentTimeMillis()
.
Отметим также, что nanoTime()
, очевидно, возвращает время в наносекундах. Следовательно, если прошедшее время измеряется в другой единице времени, мы должны преобразовать его соответствующим образом.
Например, чтобы преобразовать в миллисекунды, мы должны разделить результат в наносекундах на 1 000 000.
Еще одна ловушка с nanoTime()
заключается в том, что, несмотря на то, что он обеспечивает наносекундную точность, он не гарантирует разрешение в наносекундах (т.е. как часто значение обновляется).
Однако это гарантирует, что разрешение будет не хуже, чем у currentTimeMillis()
.
3. Ява 8
Если мы используем Java 8, мы можем попробовать новые классы java.time.Instant
и java.time.Duration
. Оба являются неизменяемыми, потокобезопасными и используют свою собственную шкалу времени, Java Time-Scale,
как и все классы в новом API java.time
.
3.1. Шкала времени Java
Традиционный способ измерения времени состоит в том, чтобы разделить день на 24 часа 60 минут 60 секунд, что дает 86 400 секунд в день. Однако солнечные сутки не всегда одинаково длинны.
Шкала времени UTC фактически позволяет суткам иметь 86,399 или 86,401 секунды SI. Секунда в системе СИ является научной «стандартной международной секундой» и определяется периодами излучения атома цезия-133). Это необходимо для того, чтобы день выровнялся с Солнцем.
Шкала времени Java делит каждый календарный день ровно на 86 400 частей, известных как секунды . Високосных секунд нет.
3.2. Мгновенный
класс
Класс Instant
представляет мгновение на временной шкале. По сути, это числовая метка времени со стандартной эпохи Java 1970-01-01T00:00:00Z
.
Чтобы получить текущую метку времени, мы можем использовать статический метод Instant.now() .
Этот метод позволяет передавать необязательный параметр Clock
. Если этот параметр опущен, используются системные часы в часовом поясе по умолчанию.
Мы можем хранить время начала и окончания в двух переменных, как в предыдущих примерах. Затем мы можем рассчитать время, прошедшее между обоими моментами.
Мы можем дополнительно использовать класс Duration и его метод
between()
, чтобы получить продолжительность между двумя объектами Instant .
Наконец, нам нужно преобразовать Duration
в миллисекунды:
Instant start = Instant.now();
// CODE HERE
Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();
4. Секундомер
Переходя к библиотекам, Apache Commons Lang предоставляет класс StopWatch
, который можно использовать для измерения прошедшего времени.
4.1. Зависимость от Maven
Мы можем получить последнюю версию, обновив pom.xml:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
Последнюю версию зависимости можно проверить здесь .
4.2. Измерение прошедшего времени с помощью секундомера
Прежде всего, нам нужно получить экземпляр класса, а затем мы можем просто измерить прошедшее время:
StopWatch watch = new StopWatch();
watch.start();
После того, как у нас запущены часы, мы можем выполнить код, который хотим протестировать, а затем, в конце, мы просто вызываем метод stop()
. Наконец, чтобы получить фактический результат, мы вызываем getTime()
:
watch.stop();
System.out.println("Time Elapsed: " + watch.getTime()); // Prints: Time Elapsed: 2501
StopWatch
имеет несколько дополнительных вспомогательных методов, которые мы можем использовать, чтобы приостановить или возобновить наши измерения. Это может быть полезно, если нам нужно сделать наш тест более сложным.
Наконец, отметим, что класс не является потокобезопасным.
5. Вывод
В Java существует множество способов измерения времени. Мы рассмотрели очень «традиционный» (и неточный) способ с использованием currentTimeMillis()
. Кроме того, мы проверили StopWatch
Apache Common и рассмотрели новые классы, доступные в Java 8.
В целом, для простых и корректных измерений прошедшего времени достаточно метода nanoTime()
. Это также короче, чем currentTimeMillis()
.
Заметим, однако, что для правильного бенчмаркинга вместо измерения времени вручную мы можем использовать фреймворк вроде Java Microbenchmark Harness (JMH). Эта тема выходит за рамки данной статьи, но мы рассмотрели ее здесь .
Наконец, как всегда, код, использованный во время обсуждения, можно найти на GitHub .