看一看1.8的HashMap---插入、查找

再看之前,需要先理解位移 先看位移运算,按照我们的日常使用-从new开始,来一步步分析源代码。(核心源码每一步都有注释)
第一步:Hash构造函数
主要两类;第一类,不指定初始化容量

public HashMap() {
    this.loadFactor = 0.75F;
}

第二类,指定初始容量和负载因子

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);
    }

可以看到在new Map的时候并没有进行任何底层数据结构的初始化,而是放到第一次put的时候。
再来看 tableSizeFor 方法,主要是返回大于等于初始化cap的最近的一个2的n次方:

//比如var0 = 5 -> 则表示 为 0000 0101
int var1 = var0 - 1; //var1 = 4,减一的目的如果 var0 = 4 也找到 8了,其实4本身已经满足了。
var1 |= var1 >>> 1; //0100 >>> 1 = 0010, 0100 | 0010 = 0110
var1 |= var1 >>> 2; //0110 >>> 2 = 0001, 0110 | 0001 = 0111
var1 |= var1 >>> 4; // 0111 >>> 4 = 0000,0000|0111 = 0111
var1 |= var1 >>> 8; // 0111 >>> 8 = 0000,0000|0111 = 0111
var1 |= var1 >>> 16; // 0111 >>> 16 = 0000,0000|0111 = 0111  此时的var1 = 7 
//因为16已经可以达到32位,继续就没意思了
return var1 < 0 ? 1 : (var1 >= 1073741824 ? 1073741824 : var1 + 1); //此处返回 8,5最近的一个2的n次方就是 8 

初始化完成后,日常使用第一步就是 put :

this.putVal(hash(var1), var1, var2, false, true);  //实际调用的是putVal 方法。
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //32的一半正好是16,高半区和低半区做异或
    }

正式进入putVal 方法:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {  //注意onlyIfAbsent参数 是否允许替换。如果当前位置已存在一个值,是否替换,false是替换,true是不替换
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0) //判断数组是否为空
            n = (tab = resize()).length; //初始化数组
        if ((p = tab[i = (n - 1) & hash]) == null) //(n - 1) & hash 计算出数组位置,因为n-1 高位为0 ,所以只考虑低位, 比如 (8-1 = 7) -> 0000 0111 , & 之后只取hash的低三位
            tab[i] = newNode(hash, key, value, null);
        else {
            //如果数组位置已经有值,p是当前数组位置的数据值
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode) //TreeNode,红黑树节点 -> 继承LinkedHashMap.Entry -> 继承HashMap.Node。在链表转成树的时候会数组位置上的元素或从Node变成TreeNode
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null); //进行链表插入
                        if (binCount >= TREEIFY_THRESHOLD - 1) 
                            treeifyBin(tab, hash); // 链表长度达到 8,还会进一步判断数组长度是否达到64,否则则转换成红黑树
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) //key已经存在
                        break;
                    p = e;
                }
            }
            if (e != null) { // 此处针对已经存在的key进行处理->值替换、返回旧值
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)  //判断是否执行值替换
                    e.value = value;
                afterNodeAccess(e); //给子类LinkedHashMap使用
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold) //数据总数超过阈值, 则开始进行扩容 比如,容量16*0.75 = 12,超过12则开始扩容。
            resize();
        afterNodeInsertion(evict); //给子类LinkedHashMap使用
        return null;
    }

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) //新容量 = 老容量*2,依然小于最大容量且老容量大于16
                newThr = oldThr << 1; // 设置新容量为老容量*2
        }
        else if (oldThr > 0) // 使用tableSizeFor之后的自定义大小为初始化容量
            newCap = oldThr;
        else {               // 使用默认值来初始化
            newCap = DEFAULT_INITIAL_CAPACITY; //容量16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //阈值 16 * 0.75
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        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;
                //循环老数组,判断数组位置上是否有数据,e是老数组位置上的元素
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //下面分别按照数组元素,链表和树来进行处理
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;// 比如,(8-1 = 7)-> 0000 0111 ,扩充后 (16 -1 = 15) -> 0000 1111。 比较 高位多了个1。因为newCap - 1 高位为0 ,所以只考虑低位,所以如果hash(比如......0111 或者 ......1111)的低位第四位为0 那么还在原来数组位置,如果为1,则移动8位。所以最终的结果要么在原位,要么移动 newCap/2。
                    else if (e instanceof TreeNode) //TreeNode,红黑树节点, 继承LinkedHashMap.Entry 继承HashMap.Node
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//和链表类似,本身树结构保有双向链表的结构,先按高低位进行拆分,得到新链表后在判断,如果长度小于8,则转成Node的链表,否则重新进行树的构建。
                    else { //处理链表
                        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) { //oldCap假设为8 -> 0000 1000,这里判断 hash低位的第四位 是否为0。如果为0,则保持原位,否者移动 oldCap = newCap/2  个位置。然后将原链表拆成 高位和低位 两个链表。
                                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;//高位,移动oldCap
                        }
                    }
                }
            }
        }
        return newTab;
    }

红黑树的插入:

final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            TreeNode<K,V> root = (parent != null) ? root() : this; //获取根节点,root方法自行看源代码
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                //开始比较
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (k != null && k.equals(pk))) //已经存在则直接返回
                    return p;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        searched = true;
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;
                    }
                    dir = tieBreakOrder(k, pk);
                }
                TreeNode<K,V> xp = p;
                //通过dir来判断 左右子节点,一直循环到左或右子节点不存在
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node<K,V> xpn = xp.next;
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    xp.next = x;
                    x.parent = x.prev = xp;
                    if (xpn != null)
                        ((TreeNode<K,V>)xpn).prev = x;
                    moveRootToFront(tab, balanceInsertion(root, x));//平衡之后在进行数组上的元素替换
                    return null;
                }
            }
        }

链表转红黑树方法:treeifyBin仅仅完成了Node单向链表转成TreeNode的双向链表

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize(); //数组长度未达到64,先进行扩容
        else if ((e = tab[index = (n - 1) & hash]) != null) { //获取数组上的链表结构第一个Node -> e
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null); //将Node转换成TreeNode
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p; //构成了双向链表
                }
                tl = p;
            } while ((e = e.next) != null);  //目前循环目的,只是将Node的单向链表,转成了TreeNode的双向链表
            if ((tab[index] = hd) != null)
                hd.treeify(tab);  //hd,双向链表第一个元素
        }
    }

TreeNode的treeify方法正式将链表转成树结构:

final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null; //清空x的左右子节点
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash; //x是链表的元素
                    Class<?> kc = null;
                    for (TreeNode<K,V> p = root;;) { //root,开始循环树插入
                        int dir, ph;
                        K pk = p.key;
                        //计算dir,判断是作为左子节点(<=0)还是右子节点(>0)
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) { //当前树节点的左或右子节点不存在,则开始进行插入处理
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x; //完成节点赋值
                            root = balanceInsertion(root, x); //开始处理插入平衡
                            break;
                        }
                    }
                }
            }
            moveRootToFront(tab, root); //主要将数组位置设置为树的根节点,同时将根节点设置为链表的首个元素
        }

红黑树的平衡-balanceInsertion方法,此处暂不分析。可以先了解一下红黑树的原理:可参考 https://www.jianshu.com/p/e136ec79235c

从以上代码来看,并没有锁机制,所以HashMap并不是线程安全的。

下面再来看看怎么来get:

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    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 && // 不管 结构是链表、树还是数组元素,最开始总从第一个开始判断。
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                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))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

到这里,相信大家对HahsMap的实现和数据结构已经有了一个比较直接的概念。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值