使用HashMap的containsKey查找键,时间复杂度为什么是O(1)?

简介:  在Java中,"containsKey"是Map接口中定义的一个方法,用于判断给定的键(key)是否存在于Map中。Map是Java中的一种数据结构,用于存储键值对(key-value pairs),其中每个键都是唯一的。

[1] 总览


 在Java中,"containsKey"是Map接口中定义的一个方法,用于判断给定的键(key)是否存在于Map中。Map是Java中的一种数据结构,用于存储键值对(key-value pairs),其中每个键都是唯一的。


 "containsKey"方法接受一个键作为参数,如果Map中存在该键,则返回true,否则返回false。


 "containsKey"方法的时间复杂度为O(1),即常数时间,因为它使用了哈希表(hash table)来实现。哈希表是一种高效的数据结构,可以在常数时间内执行插入、查找和删除操作。因此,"containsKey"方法通常用于快速检查Map中是否包含某个键,而不必遍历整个Map。


[2] 为什么hashtable可以在常数时间内执行插入、查找和删除操作?


 哈希表(hash table)可以在常数时间内执行插入、查找和删除操作,是因为它使用了哈希函数(hash function)来将键映射到索引上。


 具体来说,哈希函数将键映射到哈希表中的一个桶(bucket)上,每个桶存储了一个链表或者红黑树等数据结构,用于存储具有相同哈希值的键值对。


 在哈希表中,插入一个键值对的时间复杂度是O(1),因为只需要计算出键的哈希值,然后将它插入到对应的桶中。


 同样,查找和删除操作的时间复杂度也是O(1),因为可以根据键的哈希值直接定位到对应的桶,然后在桶中查找或删除该键值对。


 然而,如果哈希函数的设计不合理,可能会导致哈希表中的冲突(collision),即不同的键被映射到同一个桶中。这种情况下,需要使用链表或红黑树等数据结构来解决冲突。在最坏情况下,所有键都被映射到同一个桶中,导致链表或红黑树的长度变为N,此时插入、查找和删除的时间复杂度将变为O(N)。为了避免这种情况,需要使用一种好的哈希函数,使得键被平均分布到各个桶中,从而使得插入、查找和删除的平均时间复杂度保持为O(1)。


[3] HashMap的containsKey方法底层详解


/**
* 检查是否包含key
* 如果key有对应的节点对象,则返回ture,不关心节点对象的值是否为空
*/
public boolean containsKey(Object key) {
    // 调用getNode方法来获取键值对,如果没有找到返回false,找到了就返回ture
    return getNode(hash(key), key) != null; //真正的查找过程都是通过getNode方法实现的
}
/**
* 该方法是Map.get方法的具体实现
* 接收两个参数
* @param hash key的hash值,根据hash值在节点数组中寻址,该hash值是通过hash(key)得到的,可参见:hash方法解析
* @param key key对象,当存在hash碰撞时,要逐个比对是否相等
* @return 查找到则返回键值对节点对象,否则返回null
*/
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k; // 声明节点数组对象、链表的第一个节点对象、循环遍历时的当前节点对象、数组长度、节点的键对象
    // 节点数组赋值、数组长度赋值、通过位运算得到求模结果确定链表的首节点
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // 首先比对首节点,如果首节点的hash值和key的hash值相同 并且 首节点的键对象和key相同(地址相同或equals相等),则返回该节点
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first; // 返回首节点
        // 如果首节点比对不相同、那么看看是否存在下一个节点,如果存在的话,可以继续比对,如果不存在就意味着key没有匹配的键值对    
        if ((e = first.next) != null) {
            // 如果存在下一个节点 e,那么先看看这个首节点是否是个树节点
            if (first instanceof TreeNode)
                // 如果是首节点是树节点,那么遍历树来查找
                return ((TreeNode<K,V>)first).getTreeNode(hash, key); 
            // 如果首节点不是树节点,就说明还是个普通的链表,那么逐个遍历比对即可    
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) // 比对时还是先看hash值是否相同、再看地址或equals
                    return e; // 如果当前节点e的键对象和key相同,那么返回e
            } while ((e = e.next) != null); // 看看是否还有下一个节点,如果有,继续下一轮比对,否则跳出循环
        }
    }
    return null; // 在比对完了应该比对的树节点 或者全部的链表节点 都没能匹配到key,那么就返回null
}
相关文章
|
6月前
|
存储 算法 Java
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)
在阅读了上篇文章《【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(上)》之后,相信您对HashMap的基本原理和基础结构已经有了初步的认识。接下来,我们将进一步深入探索HashMap的源码,揭示其深层次的技术细节。通过这次解析,您将更深入地理解HashMap的工作原理,掌握其核心实现。
61 0
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)
|
1月前
|
Java
让星星⭐月亮告诉你,HashMap中保证红黑树根节点一定是对应链表头节点moveRootToFront()方法源码解读
当红黑树的根节点不是其对应链表的头节点时,通过调整指针的方式将其移动至链表头部。具体步骤包括:从链表中移除根节点,更新根节点及其前后节点的指针,确保根节点成为新的头节点,并保持链表结构的完整性。此过程在Java的`HashMap$TreeNode.moveRootToFront()`方法中实现,确保了高效的数据访问与管理。
29 2
|
1月前
|
Java 索引
让星星⭐月亮告诉你,HashMap之往红黑树添加元素-putTreeVal方法源码解读
本文详细解析了Java `HashMap` 中 `putTreeVal` 方法的源码,该方法用于在红黑树中添加元素。当数组索引位置已存在红黑树类型的元素时,会调用此方法。具体步骤包括:从根节点开始遍历红黑树,找到合适位置插入新元素,调整节点指针,保持红黑树平衡,并确保根节点是链表头节点。通过源码解析,帮助读者深入理解 `HashMap` 的内部实现机制。
33 2
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
52 0
|
6月前
|
存储 安全 Java
HashMap源码全面解析
HashMap源码全面解析
|
1月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
53 5
|
1月前
|
算法 索引
让星星⭐月亮告诉你,HashMap的resize()即扩容方法源码解读(已重新完善,如有不足之处,欢迎指正~)
`HashMap`的`resize()`方法主要用于数组扩容,包括初始化或加倍数组容量。该方法首先计算新的数组容量和扩容阈值,然后创建新数组。接着,旧数组中的数据根据`(e.hash & oldCap)`是否等于0被重新分配到新数组中,分为低位区和高位区两个链表,确保数据迁移时的正确性和高效性。
64 3
|
1月前
|
Java 索引
让星星⭐月亮告诉你,HashMap中红黑树TreeNode的split方法源码解读
本文详细解析了Java中`HashMap`的`TreeNode`类的`split`方法,该方法主要用于在`HashMap`扩容时将红黑树节点从旧数组迁移到新数组,并根据`(e.hash & oldCap)`的结果将节点分为低位和高位两个子树。低位子树如果元素数少于等于6,则进行去树化操作;若多于6且高位子树非空,则进行树化操作,确保数据结构的高效性。文中还介绍了`untreeify`和`replacementNode`方法,分别用于将红黑树节点转换为普通链表节点。
23 2
|
1月前
|
存储 Java
HashMap之链表转红黑树(树化 )-treefyBin方法源码解读(所有涉及到的方法均有详细解读,欢迎指正)
本文详细解析了Java HashMap中链表转红黑树的机制,包括树化条件(链表长度达8且数组长度≥64)及转换流程,确保高效处理大量数据。
71 1
|
1月前
|
存储 缓存 Java
HashMap源码解析
HashMap源码解析