← 返回文章列表

Java 中多种计算耗时方式详解

2025/6/19性能优化#Java

Java 中多种计算耗时方式详解

引言

在日常的 Java 开发过程中,性能优化是我们经常需要面对的挑战。为了准确地测量代码的执行时间,Java 提供了多种方式来实现耗时计算,从最基础的 System.currentTimeMillis() 到现代化的 StopWatch 工具类。本文将详细介绍这些方法的使用场景、优缺点以及最佳实践。

1. System.currentTimeMillis()

这是最传统也是最简单的计时方式。

基本用法

public class TimeTest {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        
        // 执行需要测量的代码
        doSomething();
        
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        System.out.println("执行耗时: " + duration + " ms");
    }
    
    private static void doSomething() {
        try {
            Thread.sleep(1000); // 模拟耗时操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

优缺点

优点

  • 简单易用,无需额外依赖
  • 返回毫秒级时间戳
  • JDK 1.0 就已存在,兼容性好

缺点

  • 精度相对较低(毫秒级)
  • 受系统时间调整影响
  • 代码冗余,需要手动计算差值

2. System.nanoTime()

提供纳秒级精度的计时方式,适合高精度测量。

基本用法

public class NanoTimeTest {
    public static void main(String[] args) {
        long startTime = System.nanoTime();
        
        // 执行需要测量的代码
        doSomething();
        
        long endTime = System.nanoTime();
        long duration = endTime - startTime;
        
        System.out.println("执行耗时: " + duration + " ns");
        System.out.println("执行耗时: " + duration / 1_000_000 + " ms");
    }
    
    private static void doSomething() {
        // 一些快速执行的代码
        for (int i = 0; i < 1000; i++) {
            Math.sqrt(i);
        }
    }
}

优缺点

优点

  • 纳秒级精度,适合微基准测试
  • 不受系统时间调整影响
  • 适合测量短时间操作

缺点

  • 返回值是相对时间,不是绝对时间戳
  • 在某些平台上精度可能达不到纳秒级

3. Spring StopWatch

Spring 框架提供的计时工具类,功能更加丰富。

基本用法

import org.springframework.util.StopWatch;

public class SpringStopWatchTest {
    public static void main(String[] args) {
        StopWatch stopWatch = new StopWatch("性能测试");
        
        // 开始计时
        stopWatch.start("任务1");
        doTask1();
        stopWatch.stop();
        
        // 第二个任务
        stopWatch.start("任务2");
        doTask2();
        stopWatch.stop();
        
        // 第三个任务
        stopWatch.start("任务3");
        doTask3();
        stopWatch.stop();
        
        // 输出详细报告
        System.out.println(stopWatch.prettyPrint());
        System.out.println("总耗时: " + stopWatch.getTotalTimeMillis() + " ms");
    }
    
    private static void doTask1() {
        try { Thread.sleep(100); } catch (InterruptedException e) {}
    }
    
    private static void doTask2() {
        try { Thread.sleep(200); } catch (InterruptedException e) {}
    }
    
    private static void doTask3() {
        try { Thread.sleep(150); } catch (InterruptedException e) {}
    }
}

输出示例

StopWatch '性能测试': running time = 450123456 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
100234567  22%   任务1
200345678  45%   任务2
149543211  33%   任务3

总耗时: 450 ms

优缺点

优点

  • 支持多个任务的分段计时
  • 提供详细的性能报告
  • 可以获取每个任务的耗时占比
  • 代码更加清晰易读

缺点

  • 需要 Spring 依赖
  • 相对简单计时有一定开销

4. Apache Commons Lang StopWatch

Apache Commons Lang 提供的计时工具。

添加依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

基本用法

import org.apache.commons.lang3.time.StopWatch;

public class CommonsStopWatchTest {
    public static void main(String[] args) {
        StopWatch stopWatch = new StopWatch();
        
        // 开始计时
        stopWatch.start();
        
        doSomething();
        
        // 暂停计时
        stopWatch.suspend();
        System.out.println("暂停时耗时: " + stopWatch.getTime() + " ms");
        
        // 恢复计时
        stopWatch.resume();
        
        doSomethingElse();
        
        // 停止计时
        stopWatch.stop();
        
        System.out.println("总耗时: " + stopWatch.getTime() + " ms");
        System.out.println("格式化输出: " + stopWatch.toString());
    }
    
    private static void doSomething() {
        try { Thread.sleep(100); } catch (InterruptedException e) {}
    }
    
    private static void doSomethingElse() {
        try { Thread.sleep(200); } catch (InterruptedException e) {}
    }
}

优缺点

优点

  • 支持暂停和恢复功能
  • 提供多种时间单位输出
  • 线程安全(synchronized)
  • 功能丰富,使用简单

缺点

  • 需要额外依赖
  • 不支持分段计时

5. Google Guava Stopwatch

Google Guava 库提供的现代化计时工具。

添加依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

基本用法

import com.google.common.base.Stopwatch;
import java.util.concurrent.TimeUnit;

public class GuavaStopwatchTest {
    public static void main(String[] args) {
        // 创建并启动计时器
        Stopwatch stopwatch = Stopwatch.createStarted();
        
        doSomething();
        
        // 获取不同单位的耗时
        long millis = stopwatch.elapsed(TimeUnit.MILLISECONDS);
        long nanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
        
        System.out.println("耗时: " + millis + " ms");
        System.out.println("耗时: " + nanos + " ns");
        System.out.println("格式化输出: " + stopwatch.toString());
        
        // 重置计时器
        stopwatch.reset();
        stopwatch.start();
        
        doSomethingElse();
        
        stopwatch.stop();
        System.out.println("第二次耗时: " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms");
    }
    
    private static void doSomething() {
        try { Thread.sleep(100); } catch (InterruptedException e) {}
    }
    
    private static void doSomethingElse() {
        try { Thread.sleep(200); } catch (InterruptedException e) {}
    }
}

优缺点

优点

  • API 设计优雅,链式调用
  • 支持多种时间单位
  • 自动格式化输出
  • 性能优秀

缺点

  • 需要 Guava 依赖
  • 不支持分段计时

6. 自定义计时工具类

结合各种方案的优点,我们可以创建一个自定义的计时工具类。

import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.List;

public class CustomStopWatch {
    private long startTime;
    private long endTime;
    private boolean running;
    private List<TaskInfo> tasks;
    private String currentTaskName;
    
    public CustomStopWatch() {
        this.tasks = new ArrayList<>();
    }
    
    public void start(String taskName) {
        if (this.running) {
            throw new IllegalStateException("StopWatch is already running");
        }
        this.currentTaskName = taskName;
        this.startTime = System.nanoTime();
        this.running = true;
    }
    
    public void stop() {
        if (!this.running) {
            throw new IllegalStateException("StopWatch is not running");
        }
        this.endTime = System.nanoTime();
        this.running = false;
        
        long duration = this.endTime - this.startTime;
        this.tasks.add(new TaskInfo(this.currentTaskName, duration));
    }
    
    public long getLastTaskTimeNanos() {
        if (tasks.isEmpty()) {
            return 0;
        }
        return tasks.get(tasks.size() - 1).duration;
    }
    
    public long getLastTaskTime(TimeUnit unit) {
        return unit.convert(getLastTaskTimeNanos(), TimeUnit.NANOSECONDS);
    }
    
    public long getTotalTimeNanos() {
        return tasks.stream().mapToLong(task -> task.duration).sum();
    }
    
    public long getTotalTime(TimeUnit unit) {
        return unit.convert(getTotalTimeNanos(), TimeUnit.NANOSECONDS);
    }
    
    public String prettyPrint() {
        StringBuilder sb = new StringBuilder();
        sb.append("CustomStopWatch: running time = ").append(getTotalTimeNanos()).append(" ns\n");
        sb.append("---------------------------------------------\n");
        sb.append(String.format("%-15s %-10s %s\n", "ns", "%", "Task name"));
        sb.append("---------------------------------------------\n");
        
        long totalTime = getTotalTimeNanos();
        for (TaskInfo task : tasks) {
            double percent = (double) task.duration / totalTime * 100;
            sb.append(String.format("%-15d %-10.0f %s\n", task.duration, percent, task.name));
        }
        
        return sb.toString();
    }
    
    private static class TaskInfo {
        private final String name;
        private final long duration;
        
        public TaskInfo(String name, long duration) {
            this.name = name;
            this.duration = duration;
        }
    }
    
    // 使用示例
    public static void main(String[] args) {
        CustomStopWatch stopWatch = new CustomStopWatch();
        
        stopWatch.start("数据库查询");
        simulateDbQuery();
        stopWatch.stop();
        
        stopWatch.start("数据处理");
        simulateDataProcessing();
        stopWatch.stop();
        
        stopWatch.start("结果输出");
        simulateOutput();
        stopWatch.stop();
        
        System.out.println(stopWatch.prettyPrint());
        System.out.println("总耗时: " + stopWatch.getTotalTime(TimeUnit.MILLISECONDS) + " ms");
    }
    
    private static void simulateDbQuery() {
        try { Thread.sleep(100); } catch (InterruptedException e) {}
    }
    
    private static void simulateDataProcessing() {
        try { Thread.sleep(200); } catch (InterruptedException e) {}
    }
    
    private static void simulateOutput() {
        try { Thread.sleep(50); } catch (InterruptedException e) {}
    }
}

7. 性能测试最佳实践

JMH (Java Microbenchmark Harness)

对于严格的性能测试,推荐使用 JMH:

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.35</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.35</version>
</dependency>
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Warmup(iterations = 3)
@Measurement(iterations = 5)
public class StringConcatBenchmark {
    
    @Benchmark
    public String stringConcat() {
        return "Hello" + " " + "World";
    }
    
    @Benchmark
    public String stringBuilder() {
        return new StringBuilder()
                .append("Hello")
                .append(" ")
                .append("World")
                .toString();
    }
    
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(StringConcatBenchmark.class.getSimpleName())
                .build();
        
        new Runner(opt).run();
    }
}

总结

方法精度易用性功能丰富度依赖适用场景
System.currentTimeMillis()毫秒⭐⭐⭐⭐⭐简单计时
System.nanoTime()纳秒⭐⭐⭐⭐高精度计时
Spring StopWatch纳秒⭐⭐⭐⭐⭐⭐⭐⭐Spring分段计时
Commons StopWatch毫秒⭐⭐⭐⭐⭐⭐⭐Commons Lang暂停/恢复
Guava Stopwatch纳秒⭐⭐⭐⭐⭐⭐⭐⭐Guava现代化API
JMH纳秒⭐⭐⭐⭐⭐⭐⭐JMH微基准测试

选择建议

  1. 简单场景:使用 System.currentTimeMillis()System.nanoTime()
  2. Spring 项目:使用 Spring StopWatch
  3. 需要暂停/恢复:使用 Apache Commons StopWatch
  4. 现代化项目:使用 Guava Stopwatch
  5. 严格性能测试:使用 JMH
  6. 自定义需求:基于 System.nanoTime() 封装自己的工具类

记住,选择合适的工具取决于你的具体需求。对于大多数日常开发场景,Spring StopWatch 或 Guava Stopwatch 都是不错的选择。