1. 概述

本文将探讨在Java数组中查找指定值的几种常见方法,并通过JMH(Java Microbenchmark Harness)进行性能测试,对比不同方法的效率差异。我们将重点关注实际开发中容易踩坑的性能陷阱,并给出优化建议。

2. 测试环境准备

为统一测试基准,我们先生成包含随机字符串的测试数组,并使用JMH注解配置测试环境:

String[] seedArray(int length) {
    String[] strings = new String[length];
    Random value = new Random();
    for (int i = 0; i < length; i++) {
        strings[i] = String.valueOf(value.nextInt());
    }
    return strings;
}

@State(Scope.Benchmark)
public static class SearchData {
    static int count = 1000;
    static String[] strings = seedArray(1000);
}

测试配置说明:

  • 使用@State注解管理测试数据生命周期
  • 固定数组长度为1000,搜索次数1000次
  • 所有测试均在相同数据集上执行

3. 基础搜索方法对比

三种最基础的数组搜索实现方案:

3.1 转List后搜索

boolean searchList(String[] strings, String searchString) {
    return Arrays.asList(SearchData.strings)
      .contains(searchString);
}

3.2 转Set后搜索

boolean searchSet(String[] strings, String searchString) {
    Set<String> stringSet = new HashSet<>(Arrays.asList(SearchData.strings));
    return stringSet.contains(searchString);
}

3.3 循环遍历搜索

boolean searchLoop(String[] strings, String searchString) {
    for (String string : SearchData.strings) {
        if (string.equals(searchString))
            return true;
    }
    return false;
}

⚠️ 性能测试结果(单位:微秒/操作):

searchArrayAllocNewList  avgt   20    937.851 ±  14.226  us/op
searchArrayAllocNewSet   avgt   20  14309.122 ± 193.844  us/op
searchArrayLoop          avgt   20    758.060 ±   9.433  us/op

关键发现

  1. ✅ 循环遍历性能最佳
  2. ❌ 每次创建新集合对象造成严重性能损耗
  3. HashSet创建开销异常大(比List慢15倍)

4. 优化方案:重用集合对象

针对基础方法的性能瓶颈,我们尝试预先创建集合对象并复用:

4.1 重用List对象

public void searchArrayReuseList() {
    List asList = Arrays.asList(SearchData.strings);
    for (int i = 0; i < SearchData.count; i++) {
        asList.contains("T");
    }
}

4.2 重用Set对象

public void searchArrayReuseSet() {
    Set asSet = new HashSet<>(Arrays.asList(SearchData.strings));
    for (int i = 0; i < SearchData.count; i++) {
        asSet.contains("T");
    }
}

🚀 优化后性能对比(单位:微秒/操作):

searchArrayLoop          avgt   20    758.060 ±   9.433  us/op
searchArrayReuseList     avgt   20    837.265 ±  11.283  us/op
searchArrayReuseSet      avgt   20     14.030 ±   0.197  us/op

核心结论

  1. ✅ 重用HashSet后性能提升1000倍!
  2. ✅ HashSet的O(1)时间复杂度优势显现
  3. ✅ 避免重复对象创建是关键优化点

5. 二分查找方案

当数组已排序时,二分查找是高效选择:

@Benchmark
public void searchArrayBinarySearch() {
    Arrays.sort(SearchData.strings);
    for (int i = 0; i < SearchData.count; i++) {
        Arrays.binarySearch(SearchData.strings, "T");
    }
}

📊 性能测试结果(单位:微秒/操作):

searchArrayBinarySearch  avgt   20     26.527 ±   0.376  us/op

方案对比: | 方法 | 时间复杂度 | 适用场景 | |------|------------|----------| | 循环遍历 | O(n) | 小数组/单次查询 | | 二分查找 | O(log n) | 已排序数组 | | HashSet | O(1) | 高频查询场景 |

6. 结论与建议

根据测试结果,我们得出以下实用建议:

  1. 高频查询场景:首选HashSet(但需预先创建并维护集合)
  2. 已排序数组:使用二分查找(排序成本需计入)
  3. 小规模数据:简单循环遍历足够高效
  4. 避免踩坑:不要在循环中重复创建集合对象

最佳实践代码模板

// 初始化时创建集合
Set<String> lookupSet = new HashSet<>(Arrays.asList(sourceArray));

// 查询时直接使用
boolean exists = lookupSet.contains(target);

完整示例代码可在GitHub获取。实际应用中需权衡初始化开销与查询频率,选择最适合的方案。


原始标题:Check if a Java Array Contains a Value | Baeldung