Skip to content

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 的幂次方
    }
}

选对数据结构

操作频率推荐选择原因
高频随机访问ArrayListO(1) 随机访问
高频头尾操作LinkedListO(1) 插入删除
高频去重HashSetO(1) 查找
高频有序遍历LinkedHashSet保持插入顺序
高频范围查询TreeMapO(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 += itemStringBuilder
字符串拼接(数组)StringBuilderString.join()
正则匹配(循环)str.matches(pattern)预编译 Pattern
集合容量默认容量预估容量
循环累加Long sum = 0Llong sum = 0L
日期解析SimpleDateFormatDateTimeFormatter
文件读写无缓冲 FileReaderBufferedReader
反射调用每次 getMethod缓存 Method

要点回顾

  • 字符串拼接:循环内用 StringBuilder,数组用 String.join()
  • 集合操作:预估容量,选择合适的数据结构
  • 避免装箱:循环中用基本类型,累加用 AtomicInteger
  • 日期时间:JDK 8+ 用 DateTimeFormatter,线程安全
  • I/O:永远用缓冲流
  • 反射:缓存 Method/Field 对象

性能优化的第一原则:先让它 work,再让它 fast。别为了优化把代码弄乱了。

基于 VitePress 构建