在Java中,如何确定一个数组是否包含特定的值?

2701

我有一个包含以下值的String[]

public static final String[] VALUES = new String[] {"AB","BC","CD","AE"};

给定字符串 s,是否有一种好的方法来测试 VALUES 是否包含 s


6
可以使用 for 循环来解决,代码如下:"for (String s : VALUES) if (s.equals("MYVALUE")) return true;"。 - Zack
3
@camickr--我遇到了一个几乎相同的情况,链接是这个:https://dev59.com/A3VC5IYBdhLWcg3wpi98#223929。它一直得到投票,但只是从sun的文档中复制粘贴而来。我猜分数是基于你提供了多少帮助,而不是你付出了多少努力——大多数情况下是你多快发布答案!也许我们已经偶然发现了John Skeet的秘密!好的回答,+1给你。 - Bill K
3
如果你正在使用Apache Commons,那么org.apache.commons.lang.ArrayUtils.contains()可以为你完成这个任务。 - Mr. Boy
50
因为像我这样的人会在谷歌上搜索问题,点击 SO 的结果,看到你的答案,测试它,发现它有效,就会点赞该答案然后离开。 - Aequitas
2
我真的很想在java.util.Arrays中找到一个简单的indexOfcontains,它们都包含直接的循环。是的,你可以在1分钟内编写它们;但我仍然去了StackOverflow,期望在JDK的某个地方找到它们。 - tucuxi
显示剩余2条评论
32个回答

3366
Arrays.asList(yourArray).contains(yourValue)

警告:对于基本类型的数组无效(请参见注释)。


自从以来,您现在可以使用流(Streams)。

String[] values = {"AB","BC","CD","AE"};
boolean contains = Arrays.stream(values).anyMatch("s"::equals);

要检查一个包含intdoublelong值的数组是否包含某个值,分别使用IntStreamDoubleStreamLongStream

示例

int[] a = {1,2,3,4};
boolean contains = IntStream.of(a).anyMatch(x -> x == 4);

112
我对这个与Arrays类中搜索函数相比以及迭代数组并使用equals()函数或==操作符来比较原始类型的性能有些好奇。 - Thomas Owens
196
asList()返回的是以数组为基础的ArrayList,因此你不会损失太多。构造函数只会更改引用,所以工作量不大。contains()/indexOf()方法会迭代并使用equals()方法。对于基本类型,最好自己编写代码。对于字符串或其他类,区别不会很明显。 - Joey
19
NetBeans声称'Arrays.asList(holidays)'对于一个'int[] holidays'返回的是一个'list<int[]>'而不是'list<int>',它只包含一个单独的元素,也就是那个int数组。这意味着Contains方法无法工作,因为它只有一个元素,即int数组。 - Nyerguds
66
Nyerguds: 的确,这对原始类型不起作用。在 Java 中,原始类型无法成为泛型。asList 声明为 <T> List<T> asList(T...)。当您将 int[] 传递给它时,编译器推断 T=int[],因为它无法推断 T=int,因为原始类型无法成为泛型。 - CromTheDestroyer
31
@Joey,顺便提一下,它是一个ArrayList,但不是你期望的java.util.ArrayList,实际返回的类是:java.util.Arrays.ArrayList<E>,定义为:public class java.util.Arrays {private static class ArrayList<E> ... {}} - TWiStErRob
显示剩余28条评论

447

Java SE 9 简明更新说明

引用数组不太好。对于这种情况,我们需要一个集合。自 Java SE 9 版本以来,我们有了 Set.of 方法。

private static final Set<String> VALUES = Set.of(
    "AB","BC","CD","AE"
);

“给定字符串s,有没有一种好的方法来测试VALUES是否包含s?”
VALUES.contains(s)

O(1).

这是最佳类型,不可变的、O(1)和简明的。太美妙了。

原始答案细节

首先清除代码。我们有(已更正):

public static final String[] VALUES = new String[] {"AB","BC","CD","AE"};

这是一个可变的静态变量,FindBugs会告诉你这是非常不好的。不要修改静态变量,也不要允许其他代码这样做。至少,该字段应该是私有的:

private static final String[] VALUES = new String[] {"AB","BC","CD","AE"};

(请注意,实际上您可以省略new String[];部分。)
参考数组仍然不好,我们需要一个集合:
private static final Set<String> VALUES = new HashSet<String>(Arrays.asList(
     new String[] {"AB","BC","CD","AE"}
));

(像我这样的多疑者可能会更放心,如果这被包装在Collections.unmodifiableSet中 - 它甚至可以被公开。)

(为了更符合品牌形象,集合API可预测地仍然缺少不可变集合类型,并且语法对我来说仍然过于冗长。)


201
创建这个集合的时间复杂度为O(N)。 - Drew Noakes
69
如果一个集合是静态的,它很可能会被使用多次。因此,初始化集合所花费的时间很可能相对于许多线性搜索的成本而言非常小。 - Xr.
3
@TomHawtin-tackline 为什么你说“特别是在这里我们想要一个集合”?在这种情况下,Set(HashSet)有什么优势?为什么“引用数组”不好(通过“引用数组”,您是否指的是由对Arrays.asList的调用生成的由数组支持的ArrayList)? - Basil Bourque
7
@nmr 一个 TreeSet 的时间复杂度为 O(log n)HashSet 的桶的平均元素数量大致恒定,至少在数组大小不超过 2^30 的情况下是如此。可能会受到硬件缓存等因素的影响,这些都会被大 O 分析所忽略。此外还需要假设哈希函数能有效地工作。 - Tom Hawtin - tackline
5
这并没有回答关于数组的问题。你只是说“不要使用数组”,这不是一个解决方案。另外,你只是说“X很糟糕”,但没有解释原因,这总是一个不好的答案。 - Minn
显示剩余8条评论

241

4
我同意,但这仍然比“自己动手做”更好,也比原始的Java方式更易读。 - Jason
46
有时你已经包含了这个库(出于其他原因),这是完全有效的答案。我正在寻找这个,而且我已经依赖于Apache Commons Lang。感谢您的回答。 - GuiSim
12
大多数安卓应用都会通过Proguard进行最小化处理,只将你所需的类和函数放入应用程序中。这使得应用程序等同于自己编写或复制Apache源代码。而那些不使用此最小化处理的人就不需要抱怨700kb或78kb :) - Kenyakorn Ketsombut
1
有时我们用激光打击蚊子。没有任何问题 ;) - user7851115
显示剩余3条评论

167

只需手动实现即可:

public static <T> boolean contains(final T[] array, final T v) {
    for (final T e : array)
        if (e == v || v != null && v.equals(e))
            return true;

    return false;
}

改进:

方法内的v != null条件是固定的,调用该方法时它总是评估为相同的布尔值。因此,如果输入的array很大,只评估一次这个条件更有效,并且我们可以基于结果在for循环内使用简化/更快的条件。改进后的contains()方法:

public static <T> boolean contains2(final T[] array, final T v) {
    if (v == null) {
        for (final T e : array)
            if (e == null)
                return true;
    } 
    else {
        for (final T e : array)
            if (e == v || v.equals(e))
                return true;
    }

    return false;
}

9
显然,我的解决方案更快,因为被接受的答案将数组包装成列表,并在该列表上调用contains()方法,而我的解决方案基本上只是执行了contains()方法要做的事情。 - icza
10
@AlastorMoody 中的 e==v 是一个参考相等性检查,非常快速。如果相同的对象(通过引用相同)在数组中,那么它将被更快地找到。如果它不是同一实例,它仍然可能与 equals() 方法所声称的相同,这就是在引用不同的情况下所检查的内容。 - icza
25
为什么这个函数不是Java的一部分呢?难怪人们说Java很臃肿……看看上面所有的答案,它们都使用了一堆库,而实际上你只需要一个for循环。现在的孩子们啊! - phreakhead
4
这是Java的一部分,参见Collection.contains(Object) - Steve Kuo
12
如果您查看ArraysArrayList的源代码,就会发现这并不一定比使用Arrays.asList(...).contains(...)更快。创建ArrayList的开销非常小,而ArrayList.contains()使用比上面显示的循环更智能的循环(实际上使用了两个不同的循环)(JDK 7)。 - Axel
显示剩余10条评论

97

四种不同的方法检查数组是否包含某个值

  1. 使用 List

  2. public static boolean useList(String[] arr, String targetValue) {
        return Arrays.asList(arr).contains(targetValue);
    }
    
  3. 使用Set

    public static boolean useSet(String[] arr, String targetValue) {
        Set<String> set = new HashSet<String>(Arrays.asList(arr));
        return set.contains(targetValue);
    }
    
  4. 使用简单循环:

    public static boolean useLoop(String[] arr, String targetValue) {
        for (String s: arr) {
            if (s.equals(targetValue))
                return true;
        }
        return false;
    }
    
  5. 使用Arrays.binarySearch():

    以下代码是错误的,这里列出仅供完整性。 binarySearch()只能用于已排序的数组。您会发现下面的结果很奇怪。当数组已排序时,这是最佳选择。

  6. public static boolean binarySearch(String[] arr, String targetValue) {  
        return Arrays.binarySearch(arr, targetValue) >= 0;
    }
    

快速示例:

String testValue="test";
String newValueNotInList="newValue";
String[] valueArray = { "this", "is", "java" , "test" };
Arrays.asList(valueArray).contains(testValue); // returns true
Arrays.asList(valueArray).contains(newValueNotInList); // returns false

5
你的二分查找示例应该返回一个大于0的值。 - Will Sherwood
7
为什么?我认为它应该返回一个大于-1的值,因为0会表示它包含在数组头部。 - mbelow
3
第一个带有 (a >= 0) 的变量是正确的,只需要查看文档,它们表示“请注意,如果找到关键字,则保证返回值>= 0”。请参考文档。 - Yoory N.
为什么 works 函数返回 String 而不是 int 呢?静态的 Boolean exists(int[] ints, int k) 函数如下:return Arrays.asList(ints).contains(k); - Willians Martins

76

如果数组没有排序,你需要迭代整个数组并对每个元素调用equals方法。

如果数组已经排序,你可以使用二分查找,在Arrays类中有一个实现。

一般来说,如果你需要进行很多成员检查,可能会更好地将所有元素存储在Set中,而不是数组中。


1
另外,就像我在我的答案中所说的那样,如果您使用Arrays类,您可以对数组进行排序,然后在新排序的数组上执行二进制搜索。 - Thomas Owens
1
@Thomas:我同意。或者你可以把所有东西都添加到TreeSet中,复杂度相同。如果不改变的话,我会使用Arrays(也许可以节省一点内存局部性,因为引用是连续的,尽管字符串不是)。如果这个会随着时间而改变,我会使用set。 - Uri

54

就我的经验而言,我进行了一个速度比较的测试。我生成随机整数,将它们转换为字符串并将它们添加到数组中。然后我搜索可能的最大数字/字符串,这将是asList().contains()的最坏情况。

当使用10K数组大小时,结果如下:

Sort & Search   : 15
Binary Search   : 0
asList.contains : 0

使用100K数组的结果如下:

Sort & Search   : 156
Binary Search   : 0
asList.contains : 32

因此,如果数组是按排序顺序创建的,则二分搜索是最快的方法;否则,asList().contains将是更好的选择。如果您有许多搜索,则对数组进行排序可能是值得的,以便您可以使用二分搜索。这完全取决于您的应用程序。

我认为这些是大多数人所期望的结果。以下是测试代码:

import java.util.*;

public class Test {
    public static void main(String args[]) {
        long start = 0;
        int size = 100000;
        String[] strings = new String[size];
        Random random = new Random();

        for (int i = 0; i < size; i++)
            strings[i] = "" + random.nextInt(size);

        start = System.currentTimeMillis();
        Arrays.sort(strings);
        System.out.println(Arrays.binarySearch(strings, "" + (size - 1)));
        System.out.println("Sort & Search : "
                + (System.currentTimeMillis() - start));

        start = System.currentTimeMillis();
        System.out.println(Arrays.binarySearch(strings, "" + (size - 1)));
        System.out.println("Search        : "
                + (System.currentTimeMillis() - start));

        start = System.currentTimeMillis();
        System.out.println(Arrays.asList(strings).contains("" + (size - 1)));
        System.out.println("Contains      : "
                + (System.currentTimeMillis() - start));
    }
}

6
我不理解这段代码。你对数组“strings”进行了排序,并在两次调用binarySearch时使用相同的(已排序)数组。这怎么能显示除热点运行时优化之外的任何东西呢?与asList.contains调用相同。您从已排序的数组中创建一个列表,然后使用最高值在其中进行contains操作。当然要花费时间。这个测试的意义是什么?更别提它是一项不合适的微基准测试了。 - Erik
此外,由于二分查找只能应用于已排序的集合,因此对于使用二分查找,排序和搜索是唯一可行的方法。 - Erik
1
排序可能已经由于许多其他原因而完成,例如,它可以在初始化时进行排序并且永远不会更改。测试搜索时间本身是有用的。然而,这种方法的缺点在于它不是微基准测试的最佳示例。在Java中,微基准测试非常难以正确执行,例如,在运行实际测试之前,应该执行足够的测试代码以获得热点优化,更不用说使用计时器多次运行实际测试代码了。示例陷阱 - Vala
9
这个测试存在缺陷,因为它在同一个JVM实例中运行了所有3个测试。后面的测试可能会受益于前面的测试预热缓存、JIT等。 - Steve Kuo
6
这个测试其实和题目无关。排序和搜索的复杂性是线性对数级别(n*log(n)),二分查找是对数级别,而ArrayUtils.contains显然是线性的。这些解决方案属于完全不同的复杂度类别,所以将它们进行比较是没有意义的。 - dragn

42

你可以使用Arrays.asList方法,以类似的方式直接将其初始化为List,而不是使用快速数组初始化语法,例如:

public static final List<String> STRINGS = Arrays.asList("firstString", "secondString" ...., "lastString");

那么你可以这样做(就像上面的例子):

STRINGS.contains("the string you want to find");

40

使用Java 8,您可以创建一个流并检查流中的任何条目是否与"s"匹配:

String[] values = {"AB","BC","CD","AE"};
boolean sInArray = Arrays.stream(values).anyMatch("s"::equals);

或者作为通用方法:

public static <T> boolean arrayContains(T[] array, T value) {
    return Arrays.stream(array).anyMatch(value::equals);
}

4
值得注意的是原始特化。 - skiwi
另外需要补充的是,anyMatch 的 JavaDoc 表示它“如果不必要确定结果,则可能不会对所有元素评估谓词”,因此在找到匹配项后可能不需要继续处理。 - mkobit

29

您可以使用 Arrays类 执行二分查找。如果您的数组未排序,您需要使用同一类中的排序函数对数组进行排序,然后通过它来搜索。


你可以使用同一类中的排序函数来完成这个任务...我应该将它添加到我的答案中。 - Thomas Owens
1
可能比 asList().contains() 方法更费成本,除非你需要经常进行该检查(但如果它只是一个静态值列表,一开始就可以排序,说实话)。 - Joey
真的。有很多变量决定哪个是最有效的。不过拥有选择是好事。 - Thomas Owens
这里有一些实现此功能的代码:https://stackoverflow.com/a/48242328/9131078 - O.O.Balance
对整个数组进行排序以进行搜索是很昂贵的。我们可以使用相同的CPU时间来执行线性搜索本身。我更喜欢在已经按排序顺序构建的集合上使用二进制搜索。 - arunwithasmile

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接