JAVA基础篇之01HashMap的原理

java.util.HashMap
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

1.7
===================================================================
1.8

数组+链表
===================================================================
数组+链表+红黑树的存储结构存储的 Node<K,V> 节点
维护的  Node<K,V>[] table 数组
通过    Node<K,V> nex 维护链表
通过    TreeNode<K,V> 维护红黑树
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash; //tips.1.维护 key的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 int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

}

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
}

hashMap 初始化容量 16
通过扩容因子DEFAULT_LOAD_FACTOR=0.75f。

    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    // threshold 阈值, 初始化table未分配时,该值就是array的初始化容量
    int threshold;

hashMap 流程
1、HashMap<String,String> map = new HashMap<String,String>();
   无参构造定义默认加载因子。此时  table = null;


   /**
   * Returns a power of two size for the given target capacity.
   */
  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;
  }
  上面一段很牛逼, 1.7中要求 capacity 值为2的倍数,但是怎么确保呢?
  jdk1.8 通过以上的方法将数字转换为最近较大的2的幂次

   map.put();

jdk1.7 hashmap 在put  key-value 键值对时,1:判断容量大小,是否需要扩容,2、找到hash对应的数组索引,3、挂载到响应位置
               在remove  key-value 键值对时,1:仅仅是对  pre节点和next节点的转换,并没有减少容量的操作
===================================================================
jdk1.8 hashmap 在put  key-value 键值对时,1:判断table.length ,是否需要扩容,如果达到max,则可以一直在下面进行挂载节点
                                          2:判断高度是否到了tree化高度 8 ,到了则指定链表转化成红黑树
                                          3:往红黑树里 put值  涉及到旋转和变色

                          remove 键值对   涉及到红黑树的自平衡,红黑树——》 链表

//table = null 或者长度为0时, 初始化table
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  有参构造传参capacity时初始化
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults  threshold
            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) {                   //oldTab  有值即为扩容时,
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;             //先将 原数组的值 置为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);  //红黑树节点的转换  这一段最好先看懂 put中红黑树节点的插入来理解
                    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;
    }


 /**
         * Tree version of putVal.
         */
        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;
            for (TreeNode<K,V> p = root;;) {                    // 根据hash 作为红黑树排序的值      left.hash  <  root.hash  < right.hash
                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)))          //排除 hash相等   key相同的情况  验证 类如果作为set或者hashMap的key要重写 hashCod和equals 方法
                    return p;
                else if ((kc == null &&                                             //key 不为自定义对象的情况, key的类要实现comparable接口
                          (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;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {       //直到找到nil java中的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));    //自平衡  并将根节点赋值到tab[index]上
                    return null;
                }
            }
        }


static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
            x.red = true;
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                // 这是一层树形结构,也就是只有一个节点
                // 直接进行着色操作
                if ((xp = x.parent) == null) {
                    x.red = false;
                    return x;
                }
                // 这里是二层树形结构,xp 存在,或者xpp 为空
                // 这里需要说明一下,有人会认为这不仅仅包含二层树形结构,可能还有三层
                // 从条件上来说是的,可能包含三层,但是这里有一个我们上面说的前提,那就是插入之前必须是红黑树,
                // 那也就是说!xp.red 意思就是 xp 为黑色节点,而且xp 是符合红黑树的,那么插入一个红色节点是不会改变它是红黑树的特性
                // 反过来想插入之前的问题
                // 这里我们把问题缩小一点,就插入点的三层树形结构。而且插入之前还必须符合红黑树
                // 如果xp 插入之前是黑色的并且符合红黑树,这种特性是不是跟只有一个根节点是一样的
                else if (!xp.red || (xpp = xp.parent) == null)
                    return root;
                    // 三层结构,如果是左分支
                if (xp == (xppl = xpp.left)) {
                    // 这里是判断前两层是否是满二叉树,也就是是否满足h <= log2(n+1)
                    // 如果满足是不需要进行 旋转的,因为无法对其旋转(左旋,右旋),
                    // 也不需要旋转,只需要进行重新着色就可以满足红黑树的五大特性了
                    if ((xppr = xpp.right) != null && xppr.red) {
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        // 接下来就是 不满足满二叉树,或者说 不满足h <= log2(n+1)
                        // 这个时候我们就需要考虑旋转了,怎样旋转呢,这里我先说结果,后面分析原因,或者为什么
                        if (x == xp.right) {
                            // 树形结构是  L — R   -->  左旋,变成 L— L树形结构
                            // x、xp、xpp 重新赋值
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            // xp 着色
                            xp.red = false;
                            if (xpp != null) {
                                // xpp 着色
                                xpp.red = true;
                                // L — L   --> 右旋
                                // 将三层树形结构  右旋 变成一个 二层结构,满足 h <= log2(n+1) 特性
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else {
                    //  这里也是一样的
                    // 判断前两层是否是满二叉树,也就是是否满足h <= log2(n+1)
                    // 如果满足是不需要进行 旋转的,因为无法对其旋转(左旋,右旋),
                    // 也不需要旋转,只需要进行重新着色就可以满足红黑树的五大特性了
                    if (xppl != null && xppl.red) {
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        // 接下来也是 不满足满二叉树,或者说 不满足h <= log2(n+1)
                        if (x == xp.left) {
                            // 树形结构是  R — L   -->  右旋,变成 R— R树形结构
                            root = rotateRight(root, x = xp);
                            // x、xp、xpp 重新赋值
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                // R — R   --> 右旋
                                // 将三层树形结构  左旋 变成一个 二层结构,满足 h <= log2(n+1) 特性
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }


https://www.jianshu.com/p/e136ec79235c  红黑二叉树机制
https://www.jianshu.com/p/c67284ef9e00  HashMap—红黑树算法结构详解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值