HashMap底层原理详解

序言

  • 是基于jdk1.8版本分析的

1.HashMap的特性

  • HashMap是一对key-value的数据结构,其规定key要是独一的,其中key和value是允许null值的

  • HashMap是线程不同步的,所以如果要使得HashMap同步,可以使用两种方法

    第一:就是保证调用HashMap的方法块同步

    第二:调用Collections.synchronizedMap()来实现该同步

    Map m = Collections.synchronizedMap(new HashMap())
        
    //最好在一开始创建HashMap的时候就调用该函数    
    
  • 从collection view返回的iterators是fail-fast,fail-fast的特点是一旦在遍历的过程中,一旦发现错误,就立即终止执行接下来的程序,立即抛出异常,而在HashMap的过程中,该iterator一旦发现modcount(在HashMapclass内部维持的一个字段,其在每次HashMap发生structural modification的时候(即,put,remove)就是加一),与初始的modcount不一致,就会抛出CorrentModificationException,中止执行

2.HashMap.class的结构

2.1HashMap的常量

  • 说明:这时HashMap设置的一些默认的常量,如果我们在创建HashMap的时候不指定,则会使用默认常量

  • 变量名(static fianl)解释
    DEFAULT_INITIAL_CAPACITY1 << 4默认的capacity([].length),如果在create HashMap时没有指定,则默认使用该值来生成比该值大一倍的threhold字段的值
    MAXIMUM_CAPACITY1<<30最大的capacity的值
    TREEIFY_THRESHOLD8如果一个node[i]链表中的node的个数大于8,则数组链表就会变成红黑树
    UNTREEIFY_THRESHOLD6目前不清楚什么意思
    MIN_TREEIFY_CAPACITY64目前不清楚什么意思
    DEFAULT_LOAD_FACTOR0.75f使得key的hash计算出的索引跟平均的分布在数组中

2.2 HashMap的常用的操作方法

  • 构造函数

    函数命和参数列表参数解释返回值返回值解释
    HashMap()无参数,使用默认的DEFAULT_LOAD_FACTORHashMap对象
    HashMap(int initialCapacity)指定initCapacity,默认使用DEFAULT_LOAD_FACTOR
    HashMap(int initialCapacity, floatloadFactor )指定initailCapacity,loadFactor
    HashMap(Map<? extends K,? extends V> m)通过一个Map实现类的实例,来生成一个HashMap
  • 常规操作

    函数和参数列表参数解释返回值返回值解释
    put(K key, V value)由putVal()函数实现,可以通过把设置onlyifabsent=true来对已存在的key的value不进行修改previous value or null插入已存在的key的时候,就会返回之前的值,如果不存在该key,则插入成功后,就会返回null
    putAll(Map<? extends K,? extends V> m)本质上是调用put()来遍历的插入无返回值
    remove(Object key)previous value or null如果由该key,则删除并返回value,如果没有value,则返回null
    remove(Object key, Object value)boolean如果该值被删除,则返回true,如果没有删除成功,则false
    clear()删除该map所有的node节点无返回值
    replace(K key, V value)previous value or nullrepalce已存在的key的时候,就会返回之前的值,如果不存在该key,则返回null
    replace(K key, V oldValue, V newValue)boolean如果该值被取代,则返回true,如果没有取代成功,则false
    get(Object key)value or null如果由该key,则返回该value,如果没有该key,则返回null
    getOrDefault(Object key, V defaultValue)value如果由key,则返回该key的值,如果没有,则返回默认值
    clone()为shallow copy
    isEmpty()是否存在node节点boolean存在:true 不存在:false
    size()由多少个nodeint
    containsKey(Object key)boolean
    containsValue(Object value)boolean
  • collection view

    values()返回value的collectionColletion 的实现类注意,该Collextion的实现是HashMap自己实现的
    keySet()返回key的SetSet的实现类该Set的实现类由HashMap自己实现
    entrySet()返回的是node的set,用该类的iteratorSet的实现类该Set的实现类由HashMap自己实现
    forEach(BiConsumer action)可以用来遍历HashMap,action是可以对每一entry的操作,action可以是lambar表达式
  • 额外

3.HashMap.Class的基本原理

  • 原理,HahMap底层的数据结构是数组列表或者是红黑树(balance tree),在插入node节点的时候,通过key的hash与capacity-1来位与来计算出在数组中的索引,如果没有遇到hash 冲突,则直接插入到数组中,如果hash 冲突,则插入到该index链的末尾,如果发该index链上的node的个数大于TREEIFY_THREHOLD(8),则转化位红黑树,如果发现node的个数大于threhold,则进行HashMap进行大一倍的扩充

  • 具体的数据结构的实现

    在HashMap中有字段transient Node<K,V>[] table;来实现该数据结构,注意该元素为Node

    Node 类的源码

    static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;
    
            Node(int hash, K key, V value, Node<K,V> next) {
                this.hash = hash;
                this.key = key;
                this.value = value;
                this.next = next;
            }
    
            public final K getKey()        { return key; }
            public final V getValue()      { return value; }
            public final String toString() { return key + "=" + value; }
    
            public final int hashCode() {
                return Objects.hashCode(key) ^ Objects.hashCode(value);
            }
    
            public final V setValue(V newValue) {
                V oldValue = value;
                value = newValue;
                return oldValue;
            }
    
            public final boolean equals(Object o) {
                if (o == this)
                    return true;
                if (o instanceof Map.Entry) {
                    Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                    if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                        return true;
                }
                return false;
            }
        }
    

4.基本源码分析

4.1 putVal()

  • 源码

       final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
           //判断,Node数据是否被创建,如没有,则创建
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
           //是发生hash 冲突
            if ((p = tab[i = (n - 1) & hash]) == null)
                //如果没有,则直接插入该值
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                //判断是否跟要插入的key相等
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    //如果相等,则赋值给e
                    e = p;
                //判断是否是否属于TreeNode
                else if (p instanceof TreeNode)
                    //调用红黑树的插入函数来进行插入操作
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    //遍历index 链表
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            //不存在,则插入到链表尾
                            p.next = newNode(hash, key, value, null);
                            //检测node节点上(不包括正在index上的节点上数)是否>=8,
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        //判断是否跟要插入的key相等,如果有,则跳出
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                //判断key是否已存在
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    //是否允许进行值的覆盖
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    //返回oldValue
                    return oldValue;
                }
            }
           //添加成功,modcount+1
            ++modCount;
           //判断size是否大于threshole,如果有,则进行容量的扩充
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
           //返回null
            return null;
        }
    
  • 自然语言分析

    1. 判断node array()是否进行初始化,如果没有,则调用resize()进行初始化

    2. 判断是否发生hash 冲突,如果没有冲突,则直接插入该node

    3. 判断是否treenode,如果是,则调用红黑树的插入函数进行该结点的插入

    4. 如果发生发生hash 冲突,判断该index的链表中是否存在相同的key

      ​ 如果存在,进行值的修改(是否进行值的赋写,有onlyifabsent值来确定),直接return

      ​ 如果不存在,则直接在插入在链表尾,判断是否index的node节点是否大于等于treeify_node,如果是,则转 变成红黑树

    5. 插入成功后,进行modcount++,以及size++,如果size>threshold,则调用resize()进行比之前大一倍的扩容

4.2 resize()

  • 源码

    final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            if (oldTab != null) {
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        oldTab[j] = null;
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                        else if (e instanceof TreeNode)
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        else { // preserve order
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }
    
  • 自然语言

4.3 removeNode()

  • 源码

     final Node<K,V> removeNode(int hash, Object key, Object value,
                                   boolean matchValue, boolean movable) {
            Node<K,V>[] tab; Node<K,V> p; int n, index;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n - 1) & hash]) != null) {
                Node<K,V> node = null, e; K k; V v;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    node = p;
                else if ((e = p.next) != null) {
                    if (p instanceof TreeNode)
                        node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                    else {
                        do {
                            if (e.hash == hash &&
                                ((k = e.key) == key ||
                                 (key != null && key.equals(k)))) {
                                node = e;
                                break;
                            }
                            p = e;
                        } while ((e = e.next) != null);
                    }
                }
                if (node != null && (!matchValue || (v = node.value) == value ||
                                     (value != null && value.equals(v)))) {
                    if (node instanceof TreeNode)
                        ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                    else if (node == p)
                        tab[index] = node.next;
                    else
                        p.next = node.next;
                    ++modCount;
                    --size;
                    afterNodeRemoval(node);
                    return node;
                }
            }
            return null;
        }
    
  • 自然语言

4.4 getNode()

  • 源码

     final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
           //判断是否有该实例以及在该index上时候有node
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
                //判断在index上的node是否等于key
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                //判断next是否位空
                if ((e = first.next) != null) {
                    //判断是否为treenode
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    do {
                        //在node list中查找,是否含有相等的
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
          //没有,则直接返回null
            return null;
        }
    
  • 自然语言

5.面试题

  • .请解释一下HashMap的参数loadFactor,它的作用是什么?

    loadFactor表示HashMap的拥挤程度,影响hash操作到同一个数组位置的概率。默认loadFactor等于0.75,当HashMap里面容纳的元素已经达到HashMap数组长度的75%时,表示HashMap太挤了,需要扩容,在HashMap的构造器中可以定制loadFactor。

  • 传统hashMap的缺点(为什么引入红黑树?):

    JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。

  • 如果两个键的hashcode相同,你如何获取值对象?

    HashCode相同,通过equals比较内容获取值对象

  • 什么必须是2的幂?如果输入值不是2的幂比如10会怎么样?

    一:可以增快哈希的计算程度,即可以key的hash通过capacity进行位与(&),可以快读的得出index

    二:在进行扩容的时候,不用直接重新计算hash值,直接根据看key的hash值多出来的一个bit是0,还是1,如果是0,则是原来的index,如果是1,则原来的index+原来的capacity

    三:可以使得key在数组中均匀分布

6.HashMap与HashTable的区别

7.different between HashMap,TreeMap,LinkedMap

  • img

7.参考的资料

  • https://www.cnblogs.com/zengcongcong/p/11295349.html
  • https://segmentfault.com/a/1190000023188711
  • https://www.baeldung.com/java-fail-safe-vs-fail-fast-iterator
  • https://www.baeldung.com/java-hashcode
  • https://dzone.com/articles/how-to-use-java-hashmap-effectively
  • https://docs.oracle.com/javase/8/docs/api/index.html
  • https://www.geeksforgeeks.org/differences-treemap-hashmap-linkedhashmap-java/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值