Java - List 的 contains 方法的性能

本文通过实验对比了Java中ArrayList的contains方法与HashSet的add方法在去重操作上的性能。结果显示,对于大量数据,HashSet的平均执行时间远低于ArrayList,主要原因是HashSet的add方法时间复杂度为O(1),而ArrayList的contains方法时间复杂度为O(n)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java - List 的 contains 方法的性能

有一个需求,对一个List中的元素,获取的所有Record字段,要求去重,并作为List返回。现在有两个方案,一个是使用ArrayList(LinkedList类似),另一个是使用HashSetArrayList使用其contains()方法来去重,HashSet调用add()方法自然会去重。具体实现如下:

package com.example.collection;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author youngbear
 * @email youngbear@aliyun.com
 * @date 2018/8/15 22:17
 * @blog https://blog.youkuaiyun.com/next_second
 * @github https://github.com/YoungBear
 * @description ArrayList 的contains方法的性能问题
 */
public class ContainsTest {

    public static final int COLUMNS = 30;
    public static final int NUMBERS = 10000;
    public static void main(String[] args) {
        List<Pair> pairs = generateData();
        System.out.println("pairs.size(): " + pairs.size());

        long beginList = System.currentTimeMillis();
        List<Long> recordUsingList = getRecordUsingList(pairs);
        long endList = System.currentTimeMillis();

        long beginSet = System.currentTimeMillis();
        List<Long> recordUsingSet = getRecordUsingSet(pairs);
        long endSet = System.currentTimeMillis();

        System.out.println("list: " + (endList - beginList) + " ms" + ", size: " + recordUsingList.size());
        System.out.println("set: " + (endSet - beginSet) + " ms" + ", size: " + recordUsingSet.size());

    }

    /**
     * 生成测试数据
     * @return
     */
    static List<Pair> generateData() {
        List<Pair> pairs = new ArrayList<Pair>();
        for (int i = 0; i < NUMBERS; i++) {
            for (int j = 0; j < COLUMNS; j++) {
                Pair pair = new Pair();
                pair.setRecord((long)i);
                pair.setName("name: " + i + ", " + j);
                pairs.add(pair);
            }
        }
        return pairs;
    }

    /**
     * 使用列表去重
     * @param list
     * @return
     */
    static List<Long> getRecordUsingList(List<Pair> list) {
        List<Long> results = new ArrayList<Long>();
        for (Pair pair : list) {
            if (!results.contains(pair.getRecord())) {
                results.add(pair.getRecord());
            }
        }
        return results;
    }

    /**
     * 使用Set去重
     * @param list
     * @return
     */
    static List<Long> getRecordUsingSet(List<Pair> list) {
        Set<Long> set = new HashSet<Long>();
        for (Pair pair : list) {
            set.add(pair.getRecord());
        }
        List<Long> results = new ArrayList<Long>(set);
        return results;
    }


    static class Pair{
        private Long record;
        private String name;

        public Long getRecord() {
            return record;
        }

        public void setRecord(Long record) {
            this.record = record;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}



输出结果为:

pairs.size(): 300000
list: 13908 ms, size: 10000
set: 31 ms, size: 10000

分析

List

由于数据中存在重复元素,所以使用contains()方法,但是,ArrayListcontains()方法会调用其indexOf()方法,在indexOf()方法里边,有一个for循环,所以,ArrayListcontains()方法的时间复杂度是O(n)(感谢涛儿儿的提醒)。

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }    

HashSet

对于HashSet,它的add()方法会自动去重,它调用的是一个mapput方法,其时间复杂度是O(1)

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

结论

所以,在大量的数据的时候,不要使用Listcontains()方法,其效率很低,可以考虑使用Set来实现。

参考

HashSet vs. List performance

HashSet vs ArrayList contains performance

更多文章

<think>好的,我现在需要解释Java中的contains方法。用户可能想了解它的用途、用法以及在不同集合中的表现。首先,我得确认contains方法在哪些类或接口中存在。记得在Collection接口里,比如List、Set这些常用集合类都有contains方法,而Map接口则没有,但它的keySet或values可能有。 接下来,要说明contains的作用。它的主要功能是检查集合中是否包含特定元素,返回布尔值。需要注意,这个方法依赖于对象的equals方法来判断是否相等。比如,如果用户自定义了一个类,没有正确重写equals和hashCode,contains可能无法按预期工作。 然后要举几个例子。比如在ArrayList中使用contains,字符串的情况,因为String已经正确实现了equals,所以可以正常工作。另一个例子是用户自定义的Person类,如果没有重写equals,即使属性相同,contains也会返回false,这时候需要提醒用户注意实现equals方法。 还要提到不同集合类的性能差异。比如ArrayListcontains是O(n),而HashSet是O(1),所以在大数据量时选择合适的集合很重要。此外,像Set这样的集合可能基于对象的hashCode,所以正确实现hashCode也是必要的。 最后,需要总结使用contains时的注意事项,比如确保元素类正确实现了equals,以及不同集合的性能考量。可能用户在使用时遇到问题,比如为什么明明元素存在却返回false,这时候需要检查equals方法是否正确覆盖。 要确保结构清晰,分点说明,并给出代码示例。注意避免使用Markdown格式,用自然的中文段落和代码注释。同时,要提醒用户注意常见的陷阱,比如没有正确实现equals方法导致的问题,以及不同集合类型的性能影响。</think>在Java中,`contains`方法是用来判断集合中是否包含指定元素的方法。它的具体行为取决于使用的集合类型,但核心逻辑是通过`equals()`方法进行元素比较。以下是分步详解: --- ### 一、方法定义 `contains`方法属于`Collection`接口(如`List`、`Set`等集合类),其标准定义为: ```java boolean contains(Object o) ``` - **作用**:检查集合中是否存在与参数`o`相等的元素。 - **返回值**:存在返回`true`,否则返回`false`。 --- ### 二、底层原理 1. **依赖`equals()`方法** 当调用`contains`时,集合会遍历内部元素,依次调用元素的`equals()`方法与目标对象比较。 **重要规则**:如果自定义类未正确重写`equals()`,默认使用`Object`类的`equals()`(即比较内存地址),可能导致逻辑错误。 2. **不同集合的实现差异** - **`ArrayList`**:遍历所有元素,时间复杂度为$O(n)$。 - **`HashSet`/`HashMap`**:基于哈希值查找,时间复杂度为$O(1)$。 - **`TreeSet`**:基于红黑树查找,时间复杂度为$O(\log n)$。 --- ### 三、代码示例 #### 示例1:基本类型包装类(如String) ```java List<String> list = new ArrayList<>(); list.add("apple"); list.add("banana"); System.out.println(list.contains("apple")); // 输出true(String已正确实现equals) System.out.println(list.contains("grape")); // 输出false ``` #### 示例2:自定义类需重写equals ```java class Person { String name; public Person(String name) { this.name = name; } // 必须重写equals和hashCode! @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return Objects.equals(name, person.name); } } Set<Person> set = new HashSet<>(); set.add(new Person("Alice")); System.out.println(set.contains(new Person("Alice"))); // 输出true(已正确实现equals) ``` --- ### 四、注意事项 1. **`null`值处理** 大多数集合允许`contains(null)`检查,但需注意空指针异常(如`TreeSet`不允许插入`null`)。 2. **Map接口的contains** - `Map`没有直接的`contains`方法,但提供: - `containsKey(Object key)`:检查键是否存在。 - `containsValue(Object value)`:检查值是否存在。 3. **性能优化** 高频查询时,优先选择`HashSet`或`HashMap`(O(1)时间复杂度),避免使用`ArrayList`的`contains`(O(n))。 --- ### 五、常见问题 **Q:为什么集合中有相同属性的对象,但contains返回false?** A:自定义类未正确重写`equals()`和`hashCode()`方法,导致对象比较失败。 --- 通过理解`contains`的底层机制和正确实现对象比较逻辑,可以高效利用此方法进行元素存在性判断。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值