ArrayList的contains方法和HasMap的containsKey效率差十倍

本文对比了使用ArrayList和HashMap在解决LeetCode两数之和问题中的性能差异。通过对ArrayList的indexOf方法和HashMap的containsKey方法的源码分析,揭示了使用HashMap能够显著提高查找效率的原因。

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

问题源自于leetcode上的一道题:

twoSum:

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution.

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
这道题 一般的解决方法是的一个比较容易想到的解决方法是:进行两次遍历,但是复杂度会达到O(n2)。一个比较好的解决方法是使用HashMap复杂度可以降低到O(n)。一开始我根据类似用HashMap的思想,使用ArrayList来做,代码如下:

[java]  view plain  copy
  1. public int[] twoSum(int[] nums, int target) {  
  2.         ArrayList<Integer> list = new ArrayList<Integer>();  
  3.           
  4.         for(int i=0; i<nums.length; i++){  
  5.             if(list.contains(target-nums[i])){  
  6.                 return new int[]{list.indexOf(target-nums[i]), i};  
  7.             }  
  8.             list.add(nums[i]);  
  9.         }  
  10.         return null;  
  11.     }  

结果发现效率和笨方法差不多,运行时间根本没有得到改善,网站上的测试用例的运行时间是56ms,仅beat百分十几。后来用HashMap来做:

[java]  view plain  copy
  1. public int[] twoSum(int[] nums, int target) {  
  2.         HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();          
  3.         for(int i=0; i<nums.length; i++){  
  4.             if(map.containsKey(target-nums[i])){  
  5.                 return new int[]{map.get(target-nums[i]), i};  
  6.             }  
  7.             map.put(nums[i], i);  
  8.         }  
  9.         return null;  
  10.     }  
效率得到明显改善:6ms,这两段代码唯一的不同之处就是一个用ArrayList存储元素,另一个用HashMap。效率相差了近10倍!

为什么会有这么大的差距呢:

原因时ArrayList使用的是数组来存储元素,而HashMap使用哈希表来存储元素。在上面两段代码中,效率拉开差距的是在contains和containsKey方法上。

ArrayList的contains方法是通过调用自己的index of方法来判断的:

[java]  view plain  copy
  1. public int indexOf(Object o) {  
  2.         if (o == null) {  
  3.             for (int i = 0; i < size; i++)  
  4.                 if (elementData[i]==null)  
  5.                     return i;  
  6.         } else {  
  7.             for (int i = 0; i < size; i++)  
  8.                 if (o.equals(elementData[i]))  
  9.                     return i;  
  10.         }  
  11.         return -1;  
  12.     }  
可以看到,在源码中,index方法把整个数组顺序遍历了一遍,这种查找方法无疑是效率最低的。

在HashMap,key被存储到hash表中,查找时是在hash表上进行查找,关于hash表的查找方法很简单这里就不赘述了。

[java]  view plain  copy
  1. public boolean containsKey(Object key) {  
  2.         return getNode(hash(key), key) != null;  
  3.     }  
[java]  view plain  copy
  1. final Node<K,V> getNode(int hash, Object key) {  
  2.         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;  
  3.         if ((tab = table) != null && (n = tab.length) > 0 &&  
  4.             (first = tab[(n - 1) & hash]) != null) {  
  5.             if (first.hash == hash && // always check first node  
  6.                 ((k = first.key) == key || (key != null && key.equals(k))))  
  7.                 return first;  
  8.             if ((e = first.next) != null) {  
  9.                 if (first instanceof TreeNode)  
  10.                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);  
  11.                 do {  
  12.                     if (e.hash == hash &&  
  13.                         ((k = e.key) == key || (key != null && key.equals(k))))  
  14.                         return e;  
  15.                 } while ((e = e.next) != null);  
  16.             }  
  17.         }  
  18.         return null;  
  19.     }  


总之,如果要用contains方法,用HashMap来代替要远远快于ArrayList,如果要遍历的话还是用ArrayList吧。
<think>好的,我现在需要回答用户关于ArrayListHashMap在判断元素存在性方面的异的问题。用户还提到要结合之前的引用内容,所以我得先回顾一下之前的引用资料,确保回答准确。 首先,用户提供的引用中提到,ArrayList是基于数组实现的,而HashMap是基于哈希表。引用1里提到,HashMap的查询速度快,因为它通过散列函数直接定位到数组的位置,减少了比较次数。而ArrayList的get(index)方法会先做rangeCheck检查索引是否有效。引用4也说明了ArrayList底层是可变数组,允许null元素,并且需要扩容。这些信息有助于分析它们在元素存在性判断上的不同。 接下来,我需要明确两者的判断方式。对于ArrayList,判断元素是否存在通常是用contains方法,内部是通过遍历数组,逐个调用equals方法比较。而HashMap判断是否存在某个键,则是先计算键的哈希值,找到对应的桶,然后在桶内的链表或红黑树中查找,使用equals方法比较。对于值的存在性,HashMap需要遍历所有entry的值,这时候性能可能较,但用户的问题可能更关注键的存在性判断。 性能方面,引用1提到HashMap在有好的散列函数时,查询速度很快,接近O(1),而ArrayListcontains方法需要遍历,时间复杂度是O(n)。所以HashMap在判断元素是否存在时性能更优,尤其是在数据量大的情况下。 还需要考虑线程安全的问题,虽然用户的问题不直接涉及,但引用2提到ArrayList线程不安全,而HashMap在多线程环境下也有问题,但这对存在性判断的性能影响不大,除非在并发修改时出现异常,但用户可能更关注单线程下的性能异。 另外,扩容机制可能影响性能,但判断存在性时,除非在扩容过程中,否则影响较小。ArrayList扩容会复制数组,而HashMap扩容会重新哈希,但这些都是添加元素时的开销,不是判断存在性的主要因素。 需要指出两者的使用场景,如果频繁需要判断元素是否存在,HashMap更合适;如果需要按索引访问或保持插入顺序,ArrayList可能更合适,但存在性检查效率低。 可能还需要提到,HashMapcontainsKeycontainsValue的性能异,containsValueArrayListcontains一样是O(n),但通常用户更多使用containsKey,所以强调键的存在性判断的高效性。 最后,结合之前的引用内容,确保回答引用了正确的来源,比如引用1引用4关于数据结构性能的部分,引用2中的面试题可能涉及这些点,可以适当引用。 总结起来,回答需要分点说明两者的判断方式,时间复杂度,性能对比,以及适用场景,同时引用提供的资料作为支持。</think>### ArrayListHashMap 判断元素存在性的异及性能比较 --- #### **一、判断方式** 1. **ArrayList** - **方法**:`contains(Object o)` - **实现逻辑**: 1. **遍历数组**:从索引 0 开始,逐个调用元素的 `equals()` 方法与目标对象比较。 2. **终止条件**:找到第一个匹配项即返回 `true`,否则遍历完整个数组后返回 `false`。 ```java public boolean contains(Object o) { return indexOf(o) >= 0; } ``` - `indexOf()` 方法的时间复杂度为 $O(n)$,最坏情况下需要遍历所有元素[^4]。 2. **HashMap** - **方法**:`containsKey(Object key)` - **实现逻辑**: 1. **计算哈希值**:通过 `hash(key)` 确定键在哈希表中的桶位置(时间复杂度 $O(1)$)。 2. **链表/红黑树查找**:在桶内遍历链表或红黑树,调用 `equals()` 方法比较键是否匹配。 - 理想情况下(哈希冲突少),时间复杂度接近 $O(1)$;最坏情况下(所有键哈希冲突)退化为 $O(n)$[^1][^4]。 - **`containsValue(Object value)`**: - 需要遍历所有桶中的所有键值对,逐个比较值,时间复杂度为 $O(n)$,性能与 `ArrayList.contains()` 类似。 --- #### **二、性能对比** | **维度** | **ArrayList.contains()** | **HashMap.containsKey()** | |-----------------|--------------------------|---------------------------| | **时间复杂度** | $O(n)$ | 平均 $O(1)$,最坏 $O(n)$ | | **数据依赖** | 元素位置(越靠前越快) | 哈希函数质量、负载因子 | | **适用场景** | 少量数据或需顺序访问 | 频繁的键存在性检查 | - **示例对比**: - 若列表有 $10^6$ 个元素,`ArrayList.contains()` 可能需最多 $10^6$ 次比较。 - `HashMap.containsKey()` 在良好哈希分布时只需 $1$ 次哈希计算少量比较[^1]。 --- #### **三、底层机制影响** 1. **ArrayList 的数组结构** - **顺序存储**:元素按插入顺序排列,无索引优化。 - **扩容开销**:添加元素时可能触发数组扩容(底层调用 `System.arraycopy()`),但**不影响 `contains()` 的性能**[^3][^4]。 2. **HashMap 的哈希表结构** - **哈希定位**:通过哈希值直接定位桶,减少无效比较。 - **冲突处理**: - Java 8 后,链表长度超过阈值(默认 8)时转换为红黑树,将最坏情况从 $O(n)$ 优化为 $O(\log n)$[^1][^2]。 - **负载因子**:默认 0.75,哈希表容量达到阈值时触发扩容(重新哈希),可能影响插入性能,但对查询性能无直接影响[^4]。 --- #### **四、选择建议** 1. **优先使用 HashMap**: - 需要频繁判断**键是否存在**(如缓存、去重)。 - 例如:统计用户是否已登录、缓存中是否存在某个键值。 2. **使用 ArrayList**: - 需要**顺序访问**或**按索引快速获取元素**(时间复杂度 $O(1)$)。 - 例如:遍历列表处理数据、通过下标直接修改元素。 --- #### **五、面试深度问题** 1. **为什么 HashMap 的键需要重写 `hashCode()` `equals()`?** - **答案**: - `hashCode()` 用于确定桶位置,`equals()` 用于精确匹配键。 - 若未正确重写,不同对象可能分配到同一桶但无法匹配,导致逻辑错误[^2][^4]。 2. **ArrayList 如何优化 `contains()` 性能?** - **答案**: - 若元素有序,可通过**二分查找**(时间复杂度 $O(\log n)$)替代遍历,但需先排序。 - 使用 `HashSet` 替代,将存在性检查复杂度降至 $O(1)$[^1]。 3. **HashMap 的哈希冲突对性能的影响?** - **答案**: - 冲突过多会导致链表过长或红黑树层级过深,降低查询效率。 - 优化方法:设计良好的哈希函数、调整初始容量负载因子[^2][^4]。 --- #### **六、总结对比表** | **技术** | **存在性检查方法** | **时间复杂度** | **适用场景** | |-------------|--------------------|----------------|------------------------------| | ArrayList | `contains()` | $O(n)$ | 小数据量、顺序访问需求 | | HashMap | `containsKey()` | 平均 $O(1)$ | 大数据量、高频键存在性检查 | --- #### **七、扩展问题** 1. 如何通过 `HashSet` 替代 `ArrayList` 的 `contains()` 方法? 2. HashMap 在哈希冲突时如何保证数据一致性? 3. ArrayList LinkedList 在 `contains()` 性能上有何异? 通过以上分析,可以清晰掌握两者在存在性检查中的核心异与适用场景[^1][^2][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值