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.csdn.net/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>好的,我现在需要处理用户关于Javacontains方法的问题。首先,用户希望了解contains方法的使用说明和示例。根据提供的引用内容,引用1到4都涉及到contains方法Java中的用法,特别是不同的集合类中的应用。 首先,我应该确定contains方法的基本定义。根据引用1,contains方法是用于判断集合中是否包含特定元素的方法,属于java.util包中的集合类如ArrayList、HashSet等的常用方法。需要明确说明它的返回类型是boolean,参数是Object类型。 接下来,需要分点说明不同集合类中的contains方法。比如ArrayList基于equals方法判断元素是否存在,而HashSet则依赖hashCode和equals方法。这部分需要详细说明两者的区别,因为用户可能不清楚不同集合的实现差异。 然后,考虑用户可能需要示例代码。引用2、3、4都提到了示例,但用户提供的引用内容中没有具体代码。不过根据常见知识,可以构造ArrayList和HashSet的示例,展示contains的使用。例如,创建一个ArrayList,添加元素,然后调用contains检查是否存在。同样适用于HashSet,并指出其高效性。 还需要注意点,比如对象的equals和hashCode方法正确重写的重要性,特别是对于自定义对象。如果用户使用自定义类,而没有正确重写这些方法contains可能无法正确工作。这部分需要强调,避免常见错误。 此外,性能方面的考虑,比如ArrayListcontains是O(n),而HashSet是O(1),这会影响在不同场景下的选择。用户可能关心如何优化性能,所以需要提及这一点。 最后,总结contains方法的应用场景,比如快速查找、数据校验等,帮助用户理解何时使用该方法。可能还需要提醒用户注意null值的处理,不同集合对null的支持情况。 检查是否有遗漏的信息,比如参数是否为null时的行为,或者集合类如LinkedList的情况。根据引用1,LinkedList也属于提到的集合类,所以可能需要简要说明LinkedListcontains方法同样基于equals方法性能也是O(n)。 确保回答结构清晰,分点说明,并给出示例代码。需要按照用户的要求,使用中文,行内公式正确(虽然这个问题可能不涉及数学公式,但保持格式正确),独立公式单独成段。不过当前问题可能不需要数学公式,所以可能不需要处理这部分。 最后生成相关问题,考虑用户可能进一步想了解如何优化contains性能,或者比较不同集合的contains效率,或者如何正确重写equals和hashCode方法。例如: 1. 如何提高ArrayListcontains方法效率? 2. 为什么HashSet的contains方法比ArrayList快? 3. 自定义对象如何使用contains方法? 确保引用标记正确,引用1到4虽然内容重复,但需要正确对应到回答中的点,比如引用1提到contains的用途和不同集合类,引用2-4可能涉及示例,但用户提供的引用内容中示例部分没有具体代码,所以可能需要自行构造示例,并在末尾添加引用标识。</think>### Javacontains方法使用说明 在Java中,`contains()` 方法用于判断集合中是否包含指定元素,返回值为布尔类型(`true`/`false`)。它是`java.util.Collection`接口定义的方法,被所有集合类(如`ArrayList`、`HashSet`、`LinkedList`等)实现。以下是详细说明及示例: --- #### 1. **方法定义** - **语法**:`boolean contains(Object o)` - **参数**:`Object o`(待检查的元素) - **返回值**:若集合包含该元素,返回`true`;否则返回`false`。 --- #### 2. **不同集合类的实现差异** - **`ArrayList`/`LinkedList`** 通过遍历元素并调用`equals()`方法逐个比较,时间复杂度为$O(n)$[^1]。 示例: ```java ArrayList<String> list = new ArrayList<>(); list.add("apple"); list.add("banana"); System.out.println(list.contains("apple")); // true System.out.println(list.contains("orange")); // false ``` - **`HashSet`/`HashMap`** 基于哈希表实现,通过`hashCode()`快速定位元素,再调用`equals()`验证,时间复杂度为$O(1)$。 示例: ```java HashSet<Integer> set = new HashSet<>(); set.add(10); set.add(20); System.out.println(set.contains(10)); // true ``` --- #### 3. **关键注意事项** - **对象必须正确重写`equals()`和`hashCode()`** 若集合存储自定义对象,需确保这两个方法被正确重写,否则`contains()`可能无法正确判断。 示例: ```java class Student { String name; int id; @Override public boolean equals(Object obj) { if (obj instanceof Student) { return this.id == ((Student) obj).id; } return false; } @Override public int hashCode() { return Objects.hash(id); } } HashSet<Student> students = new HashSet<>(); students.add(new Student("Alice", 1)); System.out.println(students.contains(new Student("Bob", 1))); // true(因为id相同) ``` - **`null`值的处理** 大部分集合类允许存储`null`,但需注意具体实现。例如,`HashSet`允许一个`null`元素,而`TreeSet`会抛出`NullPointerException`。 --- #### 4. **性能优化建议** - 频繁使用`contains()`时,优先选择`HashSet`或`HashMap`,避免使用`ArrayList`或`LinkedList`。 - 对已排序的列表,可通过二分查找(`Collections.binarySearch()`)替代`contains()`,时间复杂度降为$O(\log n)$。 --- #### 5. **典型应用场景** - **数据去重**:使用`HashSet`快速过滤重复元素。 - **权限校验**:检查用户是否在允许的列表中。 - **缓存查询**:判断缓存中是否存在目标数据。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值