源码解析HashMap和LinkedHashMap底层原理(JDK1.8)

1 篇文章 0 订阅
1 篇文章 0 订阅

首先认识一下HaspMap到底长啥样?key-value组成一个Entry对象放到数组中

手绘HashMap结构图

0、HashMap中的字段

//默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//当前 HashMap 所能容纳键值对数量的最大值,超过这个值,则需扩容
int threshold;
//node数组
transient Node<K,V>[] table;
final float loadFactor;

1、无参构造方法

 public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

2、有参构造方法

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

3、有参构造方法调用this

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                          initialCapacity);
    //大于最大容量的时候取最大容量                                     
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

4、根据容量返回一个值 当达到这个值的时候就需要扩容了

//  计算结果 例如4->4  5->8  6->8  7->8  9->16  15->16
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

5、put方法 存元素

public V put(K key, V value) {
    //根据键key计算出一个hash
    return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    //刚开始tab是等于null的,构造函数的时候是不初始化这个node数组的
    //第一次使用才初始化              
   Node<K,V>[] tab; Node<K,V> p; int n, i;
    //transient Node<K,V>[] table; 一开始为null的
    if ((tab = table) == null || (n = tab.length) == 0)
    //第一次使用的时候resize扩容个之后会得
    //到一个初始化(例如16大小的Node数组)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
    //(n - 1) & hash  拿当前数组最大角标 & 用键算出来的
    //hash值随机得到一个Node[]角标的位置 并存储元素
    //注意:这里的new Node有两个实现,HashMap自身实现
    //和LinkedHashMap集成HashMap实现
        tab[i] = newNode(hash, key, value, null);
    else {
        //当产生hash碰撞的时候
        
        Node<K,V> e; K k;
        //取出角标位置处的Node节点的hash值 如果hash值一样的话
        //如果键的hash值一样并且key的地址值不一样 那么就比较equals方法
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            //如果hash相同 并且他们的值也相同那么就直接替换旧的元素
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                //产生hash碰撞后 如果hashCode相同但是eques不同 元素还是要添加啊
                // 那么找到当前位置的node节点的下一个节点
                //如果为空的话那么直接将新的节点放在这个节点的后面形成链表
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    //e = p.next遍历这个节点  遍历一次 binCount会加1
                    //那么当binCount 超过8的时候会进行二叉树化
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //链表转平衡二叉树
                        treeifyBin(tab, hash);
                    break;
                }
                //再次判断键冲突后 遍历Node数组同一位置下 链表中的元素的
                //hash相同 比较equals是不是相同 如果相同,说明一样这个位置存过了
                //就不存了
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                //遍历的关键 把下一个节点赋给p    
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    //判断有没有超过阈值  超过的话 那么就需要扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

6、扩容

final Node<K,V>[] resize() {
    //旧的node数组
    //1、第一次执行put想HashMap中存元素那么table为null
    Node<K,V>[] oldTab = table;
    //旧的node数组的大小
    //1、第一次执行那么oldCap =0
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //1、无参构造函数进来 int oldThr = threshold=0
    //2、有参构造函数  例如 new HashMap<>(15);那么计算的
    //threshold=16
    int oldThr = threshold;
    //定义新的node数组大小和阈值
    int newCap, newThr = 0;
    //oldCap >0  Node数组已经初始化了 里面有值
    if (oldCap > 0) {
        //如果数组容量大于最大容量
        if (oldCap >= MAXIMUM_CAPACITY) {
            //那么扩容取int的最大值
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //如果没有达到最大容量那么  新的容量newCap 为原来的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
             //那么新的阈值也为原来的2倍    
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        //有参构造方法的时候 int oldThr = threshold
        //那么newCap =threshold  例如16
        newCap = oldThr;
    else {
        //1、无参构造函数创建的HashMap 那么newCap=16
        newCap = DEFAULT_INITIAL_CAPACITY;
        //1、无参构造函数创建的HashMap 计算阈值  0.75f*16
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //1、无参构造方法 newCap = DEFAULT_INITIAL_CAPACITY;
    //2、有参的话那么newThr =0
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //1、无参构造函数 threshold =0.75f*16
    //2、有参的话上面已经重新计算过了
    //3、扩容为原来的2倍
    threshold = newThr;
    //正真初始化node数组的地方
    @SuppressWarnings({"rawtypes","unchecked"})
    //1、无参构造函数进来创建默认大小为16的Node[]
    //2、扩容为原来的2倍
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        //遍历老的Node数组
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            //扩容并不一定Node数组上面每一个位置都有值
            //只不过元素超过阈值的时候需要扩容,找到老数组数组上面
            //不为空的元素
            if ((e = oldTab[j]) != null) {
                //将老Node数组j位置处置空
                oldTab[j] = null;
                if (e.next == null)
                //老的Node数组的j位置的元素不是链表的话
                    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;
                    //对链表进行分组
                    //如果值为0,将 loHead 和 loTail 指向这个节点。
                    //如果后面还有节点 hash & oldCap 为0的话,则将节点链入 
                    //loHead 指向的链表中,并将 loTail 指向该节点。
                    //如果值为非0的话,则让 hiHead 和 hiTail 指向该节点。
                    //完成遍历后,可能会得到两条链表,此时就完成了链表分组:
                    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;
}

7、链表转二叉树

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    
    // 桶数组容量小于 MIN_TREEIFY_CAPACITY=64,优先进行扩容而不是树化
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

LinkedHashMap底层实现

LinkedHashMap遍历的时候是默认按照插入顺序遍历的

在这里插入图片描述
在这里插入图片描述

HashMap的链表直接是没有联系的就是挂在Node数组中,但是LinkedHashMap中的节点是有联系的他是一个双向链表所以插入的时候是有序的

LinkedHashMap没有put方法 完全使用父类HashMap中的put方法 只不过在构架链表的时候会有不同
具体体现在

tab[i] = newNode(hash, key, value, null);

//这是LinkedHshMap的创建节点方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    //创建LinkedHashMap内部类Entry
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}
//LinkedHashMap的内部类长这样  有个before和after
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}
//新的节点进来后  维护双向链表的关系
//本质是用LinkHashMap中的head和tail节点去关联堆中的
//LinkHashMap中的内部类Entry对象 实现双向链表
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    //第一次p进来 tail是null的
    //第二次p2进来先把上一个节点的p的地址值用last存起来
    LinkedHashMap.Entry<K,V> last = tail;
    //把第一个节点p的地址值给tail
    //把第二个节点的地址值给tail
    tail = p;
    //第一次last是等于null的
    if (last == null)
         //头尾指向同一个节点
        head = p;
    else {
        //last是p也是p2的上一个节点,把p2的上一个节点指向
        //last也就是p
        p.before = last;
        //再把p2的地址值赋给p的after 这样双向链维护起来了
        last.after = p;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值