Stoper i precyzyjny pomiar czasu
- Data i czas
- poziom: średni
- aktualizacja: 16 sierpnia 2026
Odejmowanie dwóch Instant.now() działa do pierwszej korekty zegara systemowego — wtedy operacja potrafi „trwać” minus dwie sekundy. Odstępy mierz przez System.nanoTime(): to zegar monotoniczny, którego nikt nie cofnie w połowie pomiaru. Instant zostaw do znaczników kalendarzowych, czyli do pytania „kiedy to się wydarzyło”. Klasa poniżej zamyka tę zasadę w stoperze, który zatrzymasz i wznowisz bez gubienia zebranego czasu, oraz w metodzie zmierz, która oddaje Ci naraz wynik operacji i czas jej wykonania. Wszystko wraca jako Duration, więc nie musisz pamiętać, czy ta liczba to sekundy, milisekundy czy nanosekundy.
import java.time.Duration;
import java.time.Instant;
import java.util.Locale;
import java.util.function.Supplier;
import java.util.stream.LongStream;
/**
* Stoper i pomiar czasu wykonania — skryptyJAVA.pl
* Zapisz jako: PomiarCzasu.java
*/
public final class PomiarCzasu {
private static final Locale PL = Locale.forLanguageTag("pl-PL");
/** Łączy wartość zwróconą przez operację z czasem jej wykonania. */
public record Wynik<T>(T wartosc, Duration czas) {}
/** Klasa narzędziowa — obiektów nie potrzebuje. */
private PomiarCzasu() {}
/** Mierzy funkcję zegarem monotonicznym i nie gubi zwróconej wartości. */
public static <T> Wynik<T> zmierz(Supplier<T> operacja) {
long poczatek = System.nanoTime();
T wartosc = operacja.get();
long koniec = System.nanoTime();
return new Wynik<>(wartosc, Duration.ofNanos(koniec - poczatek));
}
/** Mierzy operację bez wyniku: zapis do bazy, wysyłkę pliku, czyszczenie cache. */
public static Duration zmierz(Runnable operacja) {
long poczatek = System.nanoTime();
operacja.run();
return Duration.ofNanos(System.nanoTime() - poczatek);
}
/** Dobiera jednostkę do skali wyniku: nanosekundy, milisekundy, sekundy albo mm:ss. */
public static String formatuj(Duration czas) {
if (czas.isNegative()) {
return "-" + formatuj(czas.negated());
}
long sekundy = czas.getSeconds();
if (sekundy >= 60) {
return String.format(PL, "%d:%02d min", sekundy / 60, sekundy % 60);
}
if (sekundy >= 1) {
return String.format(PL, "%.3f s", sekundy + czas.getNano() / 1_000_000_000.0);
}
if (czas.getNano() < 1_000_000) {
return czas.getNano() + " ns";
}
return String.format(PL, "%.3f ms", czas.getNano() / 1_000_000.0);
}
/** Stoper, który można zatrzymać, odczytać i ponownie uruchomić. */
public static final class Stoper {
private long startNano;
private long zebraneNano;
private boolean uruchomiony;
private Stoper() {}
/** Tworzy i natychmiast uruchamia nowy stoper. */
public static Stoper uruchom() {
Stoper stoper = new Stoper();
stoper.wznow();
return stoper;
}
/** Wznawia pomiar bez kasowania wcześniej zebranego czasu. */
public void wznow() {
if (uruchomiony) {
throw new IllegalStateException("Stoper jest już uruchomiony.");
}
startNano = System.nanoTime();
uruchomiony = true;
}
/** Zatrzymuje pomiar i zwraca łączny czas wszystkich odcinków. */
public Duration zatrzymaj() {
if (!uruchomiony) {
throw new IllegalStateException("Stoper jest już zatrzymany.");
}
zebraneNano += System.nanoTime() - startNano;
uruchomiony = false;
return odczytaj();
}
/** Zwraca aktualny wynik bez zatrzymywania stopera. */
public Duration odczytaj() {
long teraz = uruchomiony ? System.nanoTime() - startNano : 0L;
return Duration.ofNanos(zebraneNano + teraz);
}
/** Kasuje zebrany czas i nie rusza stanu: biegnący stoper liczy dalej od zera. */
public void wyzeruj() {
zebraneNano = 0L;
startNano = System.nanoTime();
}
}
private static long sumaKwadratow(int limit) {
return LongStream.rangeClosed(1, limit).map(liczba -> liczba * liczba).sum();
}
// ── PRZYKŁADY ───────────────────────────────────────────
public static void main(String[] args) throws InterruptedException {
Instant znacznikStartu = Instant.now();
Thread.sleep(30);
Instant znacznikKonca = Instant.now();
System.out.println("Od " + znacznikStartu + " do " + znacznikKonca);
// Samo Duration.toString() dałoby zapis ISO-8601 (PT0.03S) — człowiekowi pokaż formatuj().
System.out.println("Odstęp: " + formatuj(Duration.between(znacznikStartu, znacznikKonca)));
Wynik<Long> wynik = zmierz(() -> sumaKwadratow(2_000_000));
System.out.println("Suma: " + wynik.wartosc() + ", obliczenia: " + formatuj(wynik.czas()));
System.out.println("Wypchnięcie bufora: " + formatuj(zmierz(() -> System.out.flush())));
Stoper stoper = Stoper.uruchom();
Thread.sleep(20);
System.out.println("Odczyt w biegu: " + formatuj(stoper.odczytaj()));
stoper.zatrzymaj();
Thread.sleep(10); // Pauza między odcinkami nie jest doliczana.
stoper.wznow();
Thread.sleep(20);
System.out.println("Dwa odcinki: " + formatuj(stoper.zatrzymaj()));
stoper.wyzeruj();
System.out.println("Po wyzerowaniu: " + formatuj(stoper.odczytaj()));
System.out.println("Dłuższy odcinek: " + formatuj(Duration.ofMillis(93_500)));
}
}
Jak użyć tego skryptu
- Zapisz kod w pliku
PomiarCzasu.java(w Javie nazwa pliku idzie za nazwą klasy publicznej) i uruchom przezjava PomiarCzasu.java. Zobaczysz znaczniki kalendarzowe, pomiar obliczeń i stoper z pauzą. - Funkcję zwracającą wartość mierz tak:
Wynik<Dane> wynik = PomiarCzasu.zmierz(() -> wczytajDane())— dostajesz osobnowartosc()iczas(). Operację, która nic nie zwraca (zapis, wysyłka), przekaż tak samo i odbierz samoDuration. - Przy kilku etapach utwórz
Stoper.uruchom(), w trakcie zaglądaj przezodczytaj(), a przerwy rób parązatrzymaj()iwznow(). Metodawyzeruj()kasuje zebrany czas i nie rusza stanu — biegnący stoper liczy dalej od zera. - Trzymaj czas jako
Durationi dopiero na granicy aplikacji zamieniaj go na liczbę:toMillis(),toSeconds()alboformatuj, które samo dobiera jednostkę do rzędu wielkości.
Warto wiedzieć
Instant.now()iSystem.currentTimeMillis()opisują czas kalendarzowy, który system może w każdej chwili skorygować. Do czasu trwania używaj różnicy dwóch odczytówSystem.nanoTime(); jego wartości bezwzględnej nie zapisuj jako daty — sama z siebie nic nie znaczy.- Nazwa
nanoTimeopisuje jednostkę wyniku, a nie gwarantowaną rozdzielczość zegara. Na danym systemie kolejne odczyty mogą różnić się o więcej niż jedną nanosekundę. - Pojedynczy pomiar nie jest wiarygodnym benchmarkiem Javy: wpływają na niego rozgrzewanie JIT, odśmiecanie pamięci i inne procesy. Do porównywania wydajności produkcyjnego kodu użyj JMH z rozgrzewką i wieloma iteracjami.
- Jeśli mierzona lambda rzuci wyjątek,
zmierzprzepuści go dalej i czasu nie zobaczysz — w monitoringu produkcyjnym odczytuj stoper w blokufinally, żeby rejestrować także nieudane operacje. SamegoStoperanie współdziel między wątkami: jego pola nie są synchronizowane, więc każdy wątek potrzebuje własnego obiektu.
Podobne skrypty
Data i czas po polsku (java.time)
Zestaw gotowych metod na bazie java.time: formatowanie dat po polsku, bezpieczne parsowanie tekstu, obliczanie wieku, różnicy dni, dni roboczych bez weekendów oraz strefy czasowe.
początkujący Zobacz kod → Data i czasDni robocze, harmonogram i Wielkanoc
Kalendarz polskich dni roboczych: data Wielkanocy z algorytmu Meeusa, święta stałe i ruchome, liczenie dni w przedziale, wyznaczanie terminów oraz gotowe harmonogramy.
zaawansowany Zobacz kod → Data i czasStrefy czasowe i zmiana czasu
Przeliczanie chwili między strefami na ZonedDateTime, wykrywanie regułami ZoneRules godziny, której przy wiosennej zmianie czasu nie ma, i tej padającej jesienią dwa razy, oraz pułapka skrótów CET i EST.
średni Zobacz kod →