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 | 微基准测试 |
选择建议
- 简单场景:使用
System.currentTimeMillis()或System.nanoTime() - Spring 项目:使用 Spring StopWatch
- 需要暂停/恢复:使用 Apache Commons StopWatch
- 现代化项目:使用 Guava Stopwatch
- 严格性能测试:使用 JMH
- 自定义需求:基于
System.nanoTime()封装自己的工具类
记住,选择合适的工具取决于你的具体需求。对于大多数日常开发场景,Spring StopWatch 或 Guava Stopwatch 都是不错的选择。