system.nanotime() 只能成对调用取差值测耗时,需预热、多轮采样、隔离状态、防优化才能获得可靠对比结果。

直接用 System.nanoTime() 测一次就比大小,结果基本不可信——它测的不是算法本身,而是 JVM 预热、JIT 编译、GC 干扰和计时器开销的混合体。真正可靠的对比,靠的是控制变量、消除干扰、提取稳定信号。
必须成对调用,只取差值
System.nanoTime() 返回的是一个单调递增的纳秒计数器值,起点未知,绝对值没有业务意义。唯一合法用法是:在待测代码前后各调一次,相减得到耗时差。
- ✅ 正确写法:
long start = System.nanoTime(); doWork(); long end = System.nanoTime(); long ns = end - start; - ❌ 错误写法:拿 nanoTime 和 currentTimeMillis 相减、把单次结果当真、用它打日志时间戳
- 单位换算注意精度:转毫秒用
ns / 1_000_000.0(保留小数),别用整数除法丢精度;转微秒用ns / 1000.0
预热 + 多轮采样,绕过 JIT 干扰
JVM 第一次执行方法会经历类加载、解释执行、JIT 编译,前几次耗时远高于稳定态。若不预热,A 算法测的是“冷启动”,B 算法测的是“已优化”,对比完全失真。
- 先空跑或调用目标方法 10,000 次以上,确保 JIT 完成热点编译
- 正式测量至少 100 轮,每轮执行 1,000–10,000 次目标逻辑(降低计时器开销占比)
- 每轮记录总纳秒耗时,再除以次数得该轮单次均值;最终取所有轮次均值的中位数(比平均值更能抵抗 GC 或中断毛刺)
隔离状态,避免交叉污染
如果 A 和 B 共享同一个 ArrayList、静态缓存或对象实例,B 的测量可能受益于 A 触发的 CPU 缓存预热、TLB 加载甚至对象晋升到老年代,导致 B “假快”。
- 每个算法使用独立输入副本(如
arr.clone()) - 每次测量前重建全部依赖对象,不复用中间状态
- A 和 B 各自完成完整预热流程,不要共用预热轮次
防优化 + 减干扰,让结果反映真实计算
JIT 可能直接优化掉没副作用的计算;日志、对象创建、GC 等也会污染数据。
- 把计算结果赋给
volatile变量(如volatile int sink),防止被优化掉 - 测量块内不打印、不 new 对象、不触发 GC(避免 String 拼接等隐式分配)
- 单线程执行,关闭 IDE 日志、暂停后台程序,减少调度抖动
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











