HashMap源码解析jdk1.8:初始化resize,添加put,获取get

源码解析有参考以下博客:

http://www.cnblogs.com/jzb-blog/p/6637823.html

HashMap:

  以k-v键值对存储格式的容器,key,value都可以为空,key不重复,非线程安全(线程安全请使用ConcurrentHashMap);

  底层采用的是 数组+(链表 / 红黑树)结构组成; 常用的有put(),get(),size(),remove(),entrySet()等方法。

 下面是对:初始化resize,put,get的源码解析,都在源码中每一步中均有注释说明

 初始化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;                            // 新数组大小和容量
    // 1.旧数组大小大于0 (新数组大小及容量均扩大两倍 或 新数组大小扩大两倍 或 无需扩容)
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {              // 若旧数组大小>=最大容量
            threshold = Integer.MAX_VALUE;             // 数组容量为Integer最大值
            return oldTab;
        }
        // 新数组大小扩大二倍,
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 若newCap小于最大容量, 旧数组大小>=16 则新容量扩大二倍
            newThr = oldThr << 1; // double threshold
    }
    // 2.旧数组容量大于0 (新数组大小等于旧容量,未涉及新容量)
    else if (oldThr > 0) // initial capacity was placed in threshold
       // 新大小 = 旧容量
        newCap = oldThr; 
    // 3.使用默认值(新数组大小默认值16,新容量=16*0.75(默认负载因子)=12)
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 若新数组容量=0(1和2 会导致newThr==0)
    if (newThr == 0) {
        // 新容量预期值 = 新数组大小*负载因子
        float ft = (float)newCap * loadFactor;
        // 若新数组大小和ft都小于最大容量 则新容量=ft,否则等于Integer最大值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // 容量的值 = 新容量
    threshold = newThr;
    // newCap值为新数组大小创建node数组
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    // table引用newTab
    table = newTab;
    if (oldTab != null) {
        // 省略不做分析 :在新数组中添加旧数据

    } 
}

添加元素put

 1.计算hash值并调用putVal方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

2.判断数组是否为空,如果为空,初始化table数组

3.添加数据

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // tab是指将要操作的数组引用
        // p表示tab上hash值对应的Node节点
        // n表示tab长度,i表示p在tab下标
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // table为存储数据的数组
        if ((tab = table) == null || (n = tab.length) == 0)
            // 2.初始化
            n = (tab = resize()).length;
        // 3.添加数据
        // 判断key所在的Node数组元素p是否为空
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 3.1 p == null 只需新建一个Node即可
            tab[i] = newNode(hash, key, value, null);
        else {
            // e: p中node节点(表示每次p.next的Node),最终表示即将添加的key,value
            // k: p中node节点(e)的key
            HashMap.Node<K,V> e; K k;
            // 3.2 p != null
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                // 3.2.1 p第一个节点的key就等于添加的key,直接返回p
                e = p;
            else if (p instanceof HashMap.TreeNode)
                // 3.2.2 p是红黑树----todo
                e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // 3.2.3 遍历链表结构的p,每次获取p的下一个节点Node,并记录binCount(标记用于转换红黑树)
                for (int binCount = 0; ; ++binCount) {
                    // 3.2.4 p.next == null
                    if ((e = p.next) == null) { // p.next 表示获取p的下一个Node
                        // 表示已经到达节点末尾,只需要创建key,value的Node对象,并将p.nexc指向它,退出循环
                        p.next = newNode(hash, key, value, null);
                        // 若p节点大于等于8,将链表结构转化为红黑树
                        // (条件比较>=7,因为binCount从0开始计数的) ----todo
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 3.2.5 p.next.key == key (e.key等于添加的key,退出循环)
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    // 重置p,不然的话每次p.next都是p的第二个节点(因为e = p.next,加上p = e,就相当于p = p.next)
                    p = e;
                }
            }
            // 3.2.6 将value替换e中的value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                // 这个函数在hashmap中没有任何操作,是个空函数,他存在主要是为了linkedHashMap的一些后续处理工作。
                // 引用http://www.cnblogs.com/jzb-blog/p/6637823.html 原话
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 数据结构变化
        ++modCount;
        if (++size > threshold)
            // 前面提到过threshold是数组容量 若超过需要扩容
            resize();
        // 这里与前面的afterNodeAccess同理,是用于linkedHashMap的尾部操作,HashMap中并无实际意义。
        // 引用http://www.cnblogs.com/jzb-blog/p/6637823.html 原话
        afterNodeInsertion(evict);
        return null;
    }

获取get 

   通过hash值找到在table数组中的那个Node节点(该节点存有大于等于8个元素,则转化为红黑树,发生在put操作),

   红黑树:通过树形结构查找     链表:循环比较链表每一个Node的key

final HashMap.Node<K,V> getNode(int hash, Object key) {
            // table数组  first:hash值对应的Node  e:first中的其中一个 
            // n:tab数组长度  k:first中的key
            HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k;
            // 1.查询hash值对应的node节点 为null直接返回null
            if ((tab = table) != null && (n = tab.length) > 0 &&
                    (first = tab[(n - 1) & hash]) != null) {
                // 2.该hash位置的Node第一个key等于需要查询的key 返回该节点
                if (first.hash == hash && // always check first node
                        ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                // 3.获取first下一个node
                if ((e = first.next) != null) {
                    // 3.1 若是树 通过树形结构获取key对应的Node
                    if (first instanceof HashMap.TreeNode)
                        return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
                    // 3.2 否则循环比较first链表每一个Node的key
                    do {
                        if (e.hash == hash &&
                                ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }

        面试必问的源码问题,今天抽出时间也把源码理解以下并发布文章。希望有描述不准确,理解不到位的地方,

非常感谢各位,能够指点一下,有您的指点我定会更加深刻,非常感谢花费您宝贵的时间看我这篇文章。

再次注明参考博客:

http://www.cnblogs.com/jzb-blog/p/6637823.html

猜你喜欢

转载自blog.csdn.net/qq_37751454/article/details/84893973