API 性能优化
线上告警:服务响应时间从 50ms 暴涨到 2s。
你开始排查:数据库正常,网络正常,GC 正常...
最后发现,罪魁祸首是一行看似无害的代码:
java
String result = "";
for (String item : items) {
result += item; // 每次循环创建新的 StringBuilder!
}这是 Java 开发中最常见的性能陷阱之一。本质上,不是 API 慢,是你用错了方式。
字符串:拼接的艺术
循环内拼接:StringBuilder 是你的朋友
java
public class StringConcatDemo {
public static void main(String[] args) {
List<String> items = Arrays.asList("a", "b", "c", "d", "e");
// ❌ 错误:循环中用 + 拼接
String bad = "";
for (String item : items) {
bad += item;
}
// 每轮循环:String → StringBuilder → toString → String
// 5 个元素创建了 5 个 StringBuilder 和 5 个 String
// ✅ 正确:预创建 StringBuilder
StringBuilder sb = new StringBuilder(32); // 预分配容量
for (String item : items) {
sb.append(item);
}
String good = sb.toString();
// ✅ JDK 5+:编译器自动优化为 StringBuilder
// 但仅限简单场景,复杂逻辑还是要手动
}
}StringJoiner:数组拼接神器(JDK 8+)
java
public class StringJoinerDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// ❌ 旧方式
StringBuilder sb = new StringBuilder();
for (int i = 0; i < names.size(); i++) {
sb.append(names.get(i));
if (i < names.size() - 1) sb.append(", ");
}
// ✅ StringJoiner(JDK 8+)
StringJoiner joiner = new StringJoiner(", ", "[", "]");
for (String name : names) {
joiner.add(name);
}
System.out.println(joiner); // [Alice, Bob, Charlie]
// ✅ 最简单:String.join()
String result = String.join(", ", names);
System.out.println(result); // Alice, Bob, Charlie
// ✅ Stream 流式(JDK 8+)
String stream = names.stream().collect(Collectors.joining(", "));
System.out.println(stream); // Alice, Bob, Charlie
}
}预编译正则:别让正则每次编译
java
public class RegexOptimization {
// ❌ 错误:循环中编译正则
public static void validateBad(List<String> inputs) {
for (String input : inputs) {
if (input.matches("\\d{4}-\\d{2}-\\d{2}")) { // 每次调用都编译!
// 处理...
}
}
}
// ✅ 正确:预编译 Pattern
private static final Pattern DATE_PATTERN =
Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
public static void validateGood(List<String> inputs) {
for (String input : inputs) {
if (DATE_PATTERN.matcher(input).matches()) { // 复用编译结果
// 处理...
}
}
}
public static void main(String[] args) {
List<String> dates = Arrays.asList(
"2024-01-01", "2024-02-30", "invalid", "2024-03-15"
);
validateBad(dates);
validateGood(dates);
}
}集合:容量和选型
预分配容量:避免扩容的代价
java
public class CollectionCapacityDemo {
public static void main(String[] args) {
// ❌ 错误:默认容量,频繁扩容
List<String> bad = new ArrayList<>(); // 默认容量 10
for (int i = 0; i < 100000; i++) {
bad.add("item" + i); // 容量不够时扩容 1.5 倍
}
// ✅ 正确:预估容量
List<String> good = new ArrayList<>(100000);
for (int i = 0; i < 100000; i++) {
good.add("item" + i);
}
// ✅ HashMap 同样适用
Map<String, Integer> map = new HashMap<>(1024); // 2 的幂次方
}
}选对数据结构
| 操作频率 | 推荐选择 | 原因 |
|---|---|---|
| 高频随机访问 | ArrayList | O(1) 随机访问 |
| 高频头尾操作 | LinkedList | O(1) 插入删除 |
| 高频去重 | HashSet | O(1) 查找 |
| 高频有序遍历 | LinkedHashSet | 保持插入顺序 |
| 高频范围查询 | TreeMap | O(log n) 有序 |
java
public class CollectionSelection {
public static void main(String[] args) {
// ❌ 错误:用 ArrayList 做高频查找
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add("item" + i);
}
// list.contains("item9999") 每次 O(n) 遍历
// ✅ 正确:用 HashSet 做查找
Set<String> set = new HashSet<>(list);
// set.contains("item9999") O(1) 查找
// ✅ 正确:需要保持顺序的查找
Map<String, Integer> orderMap = new LinkedHashMap<>();
for (int i = 0; i < 100; i++) {
orderMap.put("item" + i, i);
}
// 既有 O(1) 查找,又保持插入顺序
}
}不可变集合:JDK 9+ 的高效选择
java
public class ImmutableCollection {
public static void main(String[] args) {
// ❌ 旧方式:创建后包装为不可变
List<String> oldWay = Collections.unmodifiableList(
new ArrayList<>(Arrays.asList("a", "b", "c"))
);
// ✅ JDK 9+:直接创建
List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b", "c");
Map<String, Integer> map = Map.of("a", 1, "b", 2);
// 优点:无需复制,内部可能有优化共享
// 缺点:不能添加/删除/修改元素
}
}包装类:避免自动装箱的陷阱
循环中的装箱:性能杀手
java
public class BoxingTrap {
// ❌ 错误:包装类累加
static long wrongSum() {
long start = System.nanoTime();
Long sum = 0L; // 包装类!
for (int i = 0; i < 1_000_000; i++) {
sum += i; // i 装箱 → 相加 → 拆箱 → 装箱
}
return System.nanoTime() - start;
}
// ✅ 正确:基本类型累加
static long correctSum() {
long start = System.nanoTime();
long sum = 0L; // 基本类型
for (int i = 0; i < 1_000_000; i++) {
sum += i; // 纯基本类型运算
}
return System.nanoTime() - start;
}
public static void main(String[] args) {
System.out.println("Wrong: " + wrongSum() + "ns");
System.out.println("Correct: " + correctSum() + "ns");
// 典型结果:Wrong 慢 10-50 倍
}
}AtomicInteger:并发累加的正确方式
java
public class AtomicCounter {
// ❌ 错误:synchronized 累加
private static int count1 = 0;
private static synchronized void increment1() {
count1++;
}
// ✅ 正确:AtomicInteger
private static AtomicInteger count2 = new AtomicInteger(0);
private static void increment2() {
count2.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
// 测试并发性能
int iterations = 1_000_000;
Thread[] threads = new Thread[10];
// synchronized 版本
for (Thread t : threads) {
t = new Thread(() -> {
for (int i = 0; i < iterations; i++) increment1();
});
t.start();
}
for (Thread t : threads) t.join();
// AtomicInteger 版本
for (Thread t : threads) {
t = new Thread(() -> {
for (int i = 0; i < iterations; i++) increment2();
});
t.start();
}
for (Thread t : threads) t.join();
// AtomicInteger 在高并发场景性能更好
}
}日期时间:JDK 8+ 的正确打开方式
SimpleDateFormat:线程杀手
java
public class DateTimeTrap {
// ❌ 错误:SimpleDateFormat 非线程安全,且循环中创建
public static List<Date> parseBad(List<String> dateStrs) {
List<Date> dates = new ArrayList<>();
for (String str : dateStrs) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
dates.add(sdf.parse(str));
} catch (ParseException e) {
// 处理异常
}
}
return dates;
}
// ❌ 更隐蔽的错误:静态 SimpleDateFormat
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");
// 多线程环境下会出问题
// ✅ 正确:DateTimeFormatter 线程安全(JDK 8+)
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static List<LocalDate> parseGood(List<String> dateStrs) {
List<LocalDate> dates = new ArrayList<>();
for (String str : dateStrs) {
dates.add(LocalDate.parse(str, FORMATTER));
}
return dates;
}
public static void main(String[] args) {
List<String> dates = Arrays.asList(
"2024-01-01", "2024-02-15", "2024-03-20"
);
parseGood(dates);
}
}I/O:缓冲是基本素养
Buffered:读写必备
java
public class IOOptimization {
// ❌ 错误:无缓冲读写
public static String readBad(String file) throws IOException {
StringBuilder sb = new StringBuilder();
try (FileReader reader = new FileReader(file)) {
int ch;
while ((ch = reader.read()) != -1) { // 每次读一个字符
sb.append((char) ch);
}
}
return sb.toString();
}
// ✅ 正确: BufferedReader
public static String readGood(String file) throws IOException {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new FileReader(file), 8192)) { // 8KB 缓冲区
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
return sb.toString();
}
// ✅ JDK 8+:Files 工具类
public static String readBest(String file) throws IOException {
return Files.readString(Path.of(file));
}
}反射:缓存是救命稻草
java
public class ReflectionCache {
// ❌ 错误:每次反射调用
public static Object invokeBad(Object target, String methodName) {
try {
Method method = target.getClass().getMethod(methodName);
return method.invoke(target);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// ✅ 正确:缓存 Method 对象
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();
public static Object invokeGood(Object target, String methodName) {
String key = target.getClass().getName() + "#" + methodName;
try {
Method method = METHOD_CACHE.computeIfAbsent(key, k -> {
try {
return target.getClass().getMethod(methodName);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
return method.invoke(target);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
}优化检查清单
| 场景 | 反模式 | 正模式 |
|---|---|---|
| 字符串拼接(循环) | str += item | StringBuilder |
| 字符串拼接(数组) | StringBuilder | String.join() |
| 正则匹配(循环) | str.matches(pattern) | 预编译 Pattern |
| 集合容量 | 默认容量 | 预估容量 |
| 循环累加 | Long sum = 0L | long sum = 0L |
| 日期解析 | SimpleDateFormat | DateTimeFormatter |
| 文件读写 | 无缓冲 FileReader | BufferedReader |
| 反射调用 | 每次 getMethod | 缓存 Method |
要点回顾
- 字符串拼接:循环内用
StringBuilder,数组用String.join() - 集合操作:预估容量,选择合适的数据结构
- 避免装箱:循环中用基本类型,累加用
AtomicInteger - 日期时间:JDK 8+ 用
DateTimeFormatter,线程安全 - I/O:永远用缓冲流
- 反射:缓存
Method/Field对象
性能优化的第一原则:先让它 work,再让它 fast。别为了优化把代码弄乱了。
