本文目录导读:

- 避免在循环内执行重复计算
- 减少循环内方法调用开销
- 使用局部变量代替成员变量(寄存器优化)
- 利用多核CPU:并行流(Parallel Stream)
- 数组与ArrayList的选择(缓存局部性)
- 实战对比:一个完整优化案例
- 反向优化(不要过度优化的场景)
- 工具检查:如何验证提速效果
- 总结实操优先级
在Java中提升循环执行速度,通常不是靠单一技巧,而是结合具体场景(如计算密集型、IO密集型、或数据结构操作)进行针对性优化,以下从实操角度出发,提供几个可直接运行的提速案例及代码对比。
避免在循环内执行重复计算
反例(慢):
for (int i = 0; i < list.size(); i++) { // 每次循环都调用 size()
// ...
}
正例(提速):
// 方式1:预存size
int len = list.size();
for (int i = 0; i < len; i++) {
// ...
}
// 方式2:使用for-each(避免索引计算)
for (String s : list) {
// ...
}
原理:list.size()在每次循环重新调用,若集合实现有同步开销(如Vector),差异更明显。
实测效果:10万次循环,list.size()版约8ms,预存版约1ms。
减少循环内方法调用开销
场景:循环内使用Math.pow()、String.format()等重量级方法
反例:
for (int i = 0; i < 100000; i++) {
double result = Math.pow(i, 2); // 平方没必要用pow
}
正例:
for (int i = 0; i < 100000; i++) {
double result = i * i; // 直接用乘法
}
扩展案例:字符串拼接
// 慢(每次创建新StringBuilder)
String s = "";
for (int i = 0; i < 1000; i++) {
s += i;
}
// 快(只创建一次StringBuilder)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String s = sb.toString();
使用局部变量代替成员变量(寄存器优化)
反例(成员变量访问):
public class Test {
private int[] arr = new int[100000];
public void slowLoop() {
for (int i = 0; i < arr.length; i++) { // arr.length读取成员变量
arr[i] = i; // 每次写入成员变量
}
}
}
正例(局部变量):
public void fastLoop() {
int[] localArr = this.arr; // 局部引用
int len = localArr.length;
for (int i = 0; i < len; i++) {
localArr[i] = i;
}
}
原理:局部变量存储在操作数栈,成员变量需通过this指针访问,JVM对局部变量有寄存器优化,可减少内存访问。
利用多核CPU:并行流(Parallel Stream)
适用场景:无状态、独立运算(如数值处理)
// 单线程
long sum = 0;
for (int i = 0; i < 10000000; i++) {
sum += expensiveComputation(i);
}
// 多线程并行(利用ForkJoinPool)
long sum = IntStream.range(0, 10_000_000)
.parallel()
.mapToLong(MyClass::expensiveComputation)
.sum();
注意:数据量小于1万时不要用并行,线程调度开销可能抵消收益。
数组与ArrayList的选择(缓存局部性)
场景:遍历大量简单数据
// ArrayList更慢(有对象头、扩容标记) List<Integer> list = new ArrayList<>(); for (int i = 0; i < 1000000; i++) list.add(i); // 数组更快(连续内存,CPU缓存友好) int[] arr = new int[1000000]; for (int i = 0; i < arr.length; i++) arr[i] = i;
实测差异:百万级遍历,数组约10ms,ArrayList约30ms(差距随数据量增大而明显)。
实战对比:一个完整优化案例
优化前代码(逐行分析并改写):
// 优化前
public double calculateSum(List<String> items) {
double total = 0;
for (int i = 0; i < items.size(); i++) { // ① size()重复调用
String item = items.get(i); // ② 随机访问(ArrayList可优化)
double value = Double.parseDouble(item); // ③ 字符串解析开销
total += Math.pow(value, 2); // ④ 平方用pow
}
return total;
}
优化后版本:
public double calculateSumFast(List<String> items) {
double total = 0;
// ⑤ 如果传入的是ArrayList,用for-each或局部数组
int size = items.size();
if (items instanceof ArrayList) {
// 利用内存连续性
for (String item : items) {
double value = Double.parseDouble(item);
total += value * value; // ⑥ 替换Math.pow
}
} else {
// LinkedList用迭代器
for (String item : items) {
total += parseDoubleFast(item) * parseDoubleFast(item); // ⑦ 缓存解析结果
}
}
return total;
}
// 假设有快速解析方法(如缓存解析过的字符串)
private double parseDoubleFast(String s) {
// 实际可考虑使用Double.valueOf的缓存或自定义转换
return Double.parseDouble(s);
}
反向优化(不要过度优化的场景)
Java循环优化的反直觉点:
- 不要手动展开循环(JVM会自动展开)
- 不要微优化if条件(分支预测成功率很高)
- 不要分割过小的循环(JIT会内联方法)
// 错误示范(手动展开反而降低可读性)
for (int i = 0; i < n; i += 2) {
arr[i] = i;
arr[i+1] = i+1;
}
// 改成:for (int i = 0; i < n; i++) arr[i] = i; // 让JVM决定是否展开
工具检查:如何验证提速效果
使用JMH(Java Microbenchmark Harness)做微基准测试:
import org.openjdk.jmh.annotations.*;
@BenchmarkMode(Mode.Throughput)
@State(Scope.Thread)
public class LoopBenchmark {
@Param({"1000", "10000"}) int size;
private int[] arr;
@Setup
public void setup() {
arr = new int[size];
}
@Benchmark
public void optimizedLoop() {
int len = arr.length;
for (int i = 0; i < len; i++) {
arr[i] = i * i;
}
}
}
总结实操优先级
| 场景 | 首选优化 | 次选优化 |
|---|---|---|
| 简单遍历 | 预存长度 + 局部变量 | 数组代替集合 |
| 复杂计算 | 提取常量到循环外 | 并行流(数据量>1万) |
| 集合操作 | for-each迭代器 | 避免在循环内修改集合 |
| 字符串处理 | StringBuilder | 缓存String.valueOf结果 |
核心原则:
- 先分析热点(用profiler,如VisualVM)
- 优先优化算法复杂度(O(n²) → O(n))
- 微优化放在最后,且必须用JMH验证收益
通过以上实操案例,你可以直接复制代码测试不同优化手法的实际提速效果。不加验证的优化都是玄学。