java中HashMap的实现原理

HashMap底层实现原理

JDK1.7及以前的版本中 ,HashMap 底层由 数组+ 链表 实现。

java中HashMap的实现原理_第1张图片

新建一个 HashMap的时候就会初始化一个数组。数组中的元素我们称为 Entry,每个 Entry其实就是一个 键值对,并且包含一个指向下一个元素的引用,这就构成了链表。当需要存储一个 键值对(Entry对象) 时,会根据hash算法来决定其在数组中的位置。当需要取出一个 Entry 对象时,也会根据 hash算法找到其在数组中的存储位置,再根据 equals 方法从该位置的链表中取出 Entry。

HashMap 结合了 ArrayList 的查询效率高的特点以及 LinkedList 插入效率高的特点,但是如果我们要存储的数据过于庞大,肯定会造成多次 哈希冲突,这样一来,链表上的节点就会堆积很多,在做查询的时候效率又会变得很低,失去了 HashMap本来的特点

 在JDK1.8 及之后的版本中,HashMap 底层由 数组 + 链表 + 红黑树 实现。当节点数不大于 8 时,还是一个链表结构,只不过插入节点时变成了尾插法,当节点数大于 8 之后,将链表结构转换为红黑树结构,复杂度也从 O(n) 变成了 O(logn).

java中HashMap的实现原理_第2张图片

HashMap.put

put方法在hashmap中起插入的作用,即在当前的hashmap上添加新元素。

主要流程:

  1. 判断table数组是否为空,或者为 null,否则执行 resize() 扩容操作
  2. 使用 key.hashcode() 将 key 转换为 数组下标 i , 如果 table[i] =null,直接新建节点添加即可,转入 6 ;如果 table[i] != null,则转向 3;
  3. 判断 table[i] 的首个元素是否和 key 一样,如果相同(equals判断)则直接覆盖 value,否则转向 4
  4. 判断 table[i] 是否为 红黑二叉树,如果是,则直接插入键值对,否则转向 5
  5. 遍历 table[i],判断链表长度是否大于 8 ,若是 则把链表转换为 红黑树,进行插入操作;否则进行链表插入操作;如果链表中已存在 key 则直接覆盖 value
  6. 插入成功后,判断实际存在的键值对数量是否超过了 threshold ,如果操作则需要扩容。
public v put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    // 通过 key 计算hash值
    int hash = hash(key);
    // 通过 key 找到数组中待存放的下标
    int i = indexFor(hash, table.length);
    // 链表
    for (Entry e = table[i]; e != null; e = e.next) {
        object k;
        // 如果这个数组下标中有元素,开始遍历链表
        if (e.hash == hash && ((k = e.key) == key || key .equals(k))) {
            // 如果两个 hash 值相同,或者键相同,则修改 value
            V oldValue = e.value;
            e.value = Value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    // 可能两个 hash 值不同,但计算出的 index 一样,这就发生了 hash碰撞
    // 使用头插法,将其放在链表头部
    addEntry (hash, key, value, i)
    return null;
}

HashMap.get

get方法在hashmap中起取值的作用,即根据 key 获取对应的 value值

主要流程:

  1. 首先判断数组是否为空,如果为空直接返回null;
  2. 使用 hashcode() 计算 key的索引 i ,如果 table[i] 为空,返回null;否则判断key值是否相同,若相同则返回该key的value
  3. 如果 key 值不相同,则往后遍历;
  4. 如果 table[i] 是红黑树,则调用红黑树的 getTreeNode() 进行查找
  5. 如果 table[i] 是链表,则遍历链表查找
public V get(Object key) {
        Node e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

final Node getNode(int hash, Object key) {
        Node[] tab; Node first, e; int n; K k;
        //若哈希表不为空,并且当前索引位置有元素
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            //若在一个桶中,并且当前索引位置的key值与要查找的key值相等
            //说明找到了,返回该值
            if (first.hash == hash && 
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
                //若key值不相同,且还有元素
            if ((e = first.next) != null) {
                //如果此时已经树化,则调用红黑树的getTreeNode()查找
                if (first instanceof TreeNode)
                    return ((TreeNode)first).getTreeNode(hash, key);
                do {
                    //若没有树化,则在从前往后遍历链表,直到找到该元素
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        //若找不到则返回null
        return null;
    }

你可能感兴趣的:(Java,java,开发语言)