HashMap 源码细节解析

分析JDK版本:15.0.2

new HashMap<>(); 初始化过程

  • 默认无参数构造器
    /**
     * Constructs an empty {@code HashMap} with the default initial capacity
     * (16) and the default load factor (0.75).
     * 默认初始化容器大小为 16 默认初始化 loadFactor 为 0.75
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

首次 hashMap.put(“key”, “value”); 创建空间过程
  • 默认调用下面的 put 函数
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

  • hash(key) 函数如下
	// 先拿到 Object 的 hashCode 值
	// >>> 无符号右移 16位 高位补 0 
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  • 最后都调用到了 putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
        	// 	首次put resize() 方法 下面有介绍 resize 首次创建大小为 12 (0.75 * 16)
            n = (tab = resize()).length;

        // 然后 保证 tab 不为null以后 从 tab 中取当前要放入的数组位置是否已经有数据,如果没有数据会走if (首次一定为null)
        if ((p = tab[i = (n - 1) & hash]) == null)
        	// 直接给 tab 的 i = (n - 1) & hash]位置 放入需要插入的值。
            tab[i] = newNode(hash, key, value, null);
        else {
        	// 如果此时数组的位置上已经有 Node 了,那么将会往链表后面插入数据
            Node<K,V> e; K k;
            // 如果当前位置的Node 的hash 值和将要插入的 keuy的hash值和key做对比 ; 如果 hash相等并且key也相等,那么直接把当前位置的 key 对应的 value 替换为新的value就可以了。
            if (p.hash == hash && 
            	((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
           		// 如果目前是树结构;(1.8以后链表大小超过 TREEIFY_THRESHOLD-1 也就是7 则转换为红黑树)
           		// 下面会介绍红黑树的 putTreeVal 
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            	// 如果是插入的新值 启动一个 for(;;) 无限循环
                for (int binCount = 0; ; ++binCount) {
                	// p 是上面 if 里面赋值的当前 hash位置上的值 p = tab[i = (n - 1) & hash];(实际上就是 P 为当前数组位置对应链表的首个值)
                	// 判断 p 是不是链表的最后一个节点;
                	// 并且给 e 不断赋值;
                    if ((e = p.next) == null) {
                    	// 如果 P 是当前链表的最后一个节点;则new 一个 Node 放在当前节点后面;
                        p.next = newNode(hash, key, value, null);
                        // 如果链表增加之后 链表的长度 >= 7 则将数组转换为 红黑树 下面会有介绍
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) 
                    	// 如果不是链表最后一个节点;判断循环到的key和hash相等,直接终止循环
                        break;
                    // 将 e (e 为上面循if里面赋值的 p.next) 赋值给 p,保证下次循环能拿到下一个 p.next()  实际上就是 p = p.next() 
                    p = e;
                }
            }
            // 上面else里面如果 是在 if(e = p.next) == null) 里面break终止的循环,则 e 此时为null,如果是在下面的if break的循环,此时 e != null
            if (e != null) { // existing mapping for key
            	// 拿到 e 的 value
                V oldValue = e.value;
                // onlyIfAbsent 默认为false 所以默认一定会走 e.value = value; 否则 如果onlyIfAbsent初始化的时候传入的是 true 那么 只有 oldValue == null 的时候做赋值操作;
                // onlyIfAbsent 的作用就是hashMap只重新赋值 key 映射到的 value为null 的值。
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                // afterNodeAccess 这个HashMap没有做实现;LinkedHashMap 对这个方法做了实现,将插入的值对应的节点移动到最后
                afterNodeAccess(e);
                // 如果是修改的 oldValue 并没有新增数据,则没有必要去走下面的数量计数 和 扩容判断等操作
                return oldValue;
            }
        }
        // modCount的作用是为了防止并发操作的计数器;例如:如果你在 forEach 的时候去对Map做增删操作的时候 会抛出 throw new ConcurrentModificationException(); 异常就是靠这个参数做判断的。
        ++modCount;
        // 如果增加后的 size > 上一次扩容后的 threshold 值
        if (++size > threshold)
        	// 进行扩容操作;下面有扩容的介绍
            resize();
        //这个方法 HashMap也没有实现 LinkedHashMap 实现的方法。
        afterNodeInsertion(evict);
        return null;
    }
  • 上段代码:首次 putValue ,当 tab = table) == null 的时候;执行了 tab = resize() 方法;这是首次创建空间
// 这段代码只首次调用 resize 分配空间。
final Node<K,V>[] resize() {
		// 拿到最新的 table
        Node<K,V>[] oldTab = table;
        // 初次 oldCap = 0;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // threshold 初次为 0 如果初始化不走默认无参数构造器,默认会赋值;
        int oldThr = threshold;
        int newCap, newThr = 0;
        // oldCap > 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
        }
        // 这个参数首次 采用 无参数构造器创建的 HashMap对象 默认也是0 
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               
        	// 所以首次 new HashMap<>(); 走的这个else分支;

        	// zero initial threshold signifies using defaults
        	// 默认为 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 
            newCap = DEFAULT_INITIAL_CAPACITY; 
            // static final float DEFAULT_LOAD_FACTOR = 0.75f; 
            // newThr 默认大小为:0.75 * 16 = 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 首次为 12 所以这个 if 不走
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        // 首次确定阈值为 newThr 也就是 12;如果不是首次会通过原始数组大小 和 是否需要扩容来判断大小
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        // 创建一个大小为 12 的 Node 数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        // 为 table 赋值,赋值之后下次再put 时的 oldTab 就是这个对象了;Node是每个散列之后的链表对象
        table = newTab;
        // 因为首次 oldTab == null 所以下面代码首次不走;下面有非首次 resize() 的代码解释
        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;
    }

resize() 总结首次调用: putValue 时,会默认创建阈值为12的 Node[]数组。

非首次调用 hashMap.put(“key”, “value”); 插入值和扩容的逻辑

  • 非首次调用 put 方法,putVal() 函数还是上面的那个函数,上面已经大体解释清楚,这里主要解释调用 resize() 时需要扩容的情况。
  • 下面是 resize() 方法,接下来主要讲述扩容的过程
    final Node<K,V>[] resize() {
    	// 非首次插入调用 resize() 时,table一定不是空的
        Node<K,V>[] oldTab = table;
        // oldCap 记录的是 oldTab 长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // threshold 上面有记录,首次 resize() 的时候进行赋值,首次赋值时 0.75 * 16 = 12;所以 oldThr = 12;
        // 先看看 threshold的解释:The next size value at which to resize (capacity * load factor).
        int oldThr = threshold;
        // 定义变量 记录新的 容量和大小
        int newCap, newThr = 0;
        // oldCap > 0 非首次时 true 所以会走 if() 的代码
        if (oldCap > 0) {
        	// 判断旧容量是否超过了最大值 1 << 30 
            if (oldCap >= MAXIMUM_CAPACITY) {
            	// 如果超过了最大值则大小设置为 Integer.MAX_VALUE 直接返回了 意思就是不能继续扩容了,一般不会超过的;
            	// 也是因为下面的扩容规则,每次扩容都需要 << 1 所以超过了 MAXIMUM_CAPACITY 再<< 1 就超过了 Int的最大值了。
            	// MAXIMUM_CAPACITY = 1073741824  Integer.MAX_VALUE = 2147483647 
            	// MAXIMUM_CAPACITY << 1 = 2147483648 刚好超过了 Integer.MAX_VALUE 
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 当 oldCap < MAXIMUM_CAPACITY 的时候 并且上次初始化的值(oldCap >= DEFAULT_INITIAL_CAPACITY)16
            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
        	// 首次初始化传入了 threshold 值的时候走的,第二次不走
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
        	// 这个也是首次无参数构造器逻辑,第二次扩容不走
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // if 条件不满足
        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
        table = newTab;
        // 第二次扩容的话 oldTab 一定不为 null
        if (oldTab != null) {
        	// 下面就是扩容后将 oldTab 数据 复制到 newTab中

        	// 首先循环 oldCap
            for (int j = 0; j < oldCap; ++j) {
            	// 定义临时变量,拿到每次循环对应的 Node 节点;
                Node<K,V> e;
                // 拿到 数组的第 j 个节点的 Node 赋值给 e ;如果 == null 的话代表 当前数组位还没有值
                if ((e = oldTab[j]) != null) {
                	// 将原数组的当前值清空
                    oldTab[j] = null;
                   	// 判断当前数组位的链表是否有下一个值
                    if (e.next == null)
                    	// 如果链表只有一个值,则扩容后的容器通过 e.hash & (newCap - 1) 运算把取出的 e 放到对应的位置
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                    	// 如果取出来的是是 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 {
                        	// 拿到当前 Node 的下一个 Node
                            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 循环到链表的最后一个节点为止
                        } while ((e = next) != null);
                        // 不需要移动的节点直接的头节点放在 原来 J 的位置
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        // 需要移动的节点的头节点 放到对应的位置
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

分析 get 流程:hashMap.get(“key”);

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(key)) == null ? null : e.value;
    }

  • 重点是看 getNode(key) 方法如何获取的 Node 节点。
    final Node<K,V> getNode(Object key) {
        Node<K,V>[] tab; 
        Node<K,V> first, e; 
        int n, hash; 
        K k;
        if (
        	// 拿到当前 table 赋值给 tab
        	(tab = table) != null 
        	// 记录 table 的长度
        	&& (n = tab.length) > 0 
        	// 通过 (n - 1) & (hash = hash(key)) 位运算去匹配当前key 对应到了桶的哪个位置,然后取出对应位置的 head 节点
        	&& (first = tab[(n - 1) & (hash = hash(key))]) != null) {

        	// 用取出的 Node 存储的hash对比传入的 hash和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) {
            	// 如果是 tree 结构,从 Tree中读取
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                	// 取出的 Node key 和 key的hash 都相等 则返回 
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                  // while继续取下一个 Node...
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

分析 remove 流程:hashMap.remove(“key”);

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

  • 看 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;
        // 还是先拿到 table 赋值给 tab;tab的长度赋值给 n
        // p 为 通过需要remove的key的hash 拿到对应桶节点的链表的 head 如果head为null 则代表需要移除的key没有对应值
        if ((tab = table) != null 
        	&& (n = tab.length) > 0 
        	// 通过 Hash 计算当前要操作的 key 在桶的哪个位置 ,如果存在则将链表head节点赋值给 p 
        	&&(p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
        	// 判断如果当前拿出的p 是否是要操作的节点
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 如果head就是要找的数据 则 直接赋值给node 
                node = p;
            else if ((e = p.next) != null) {
            	// 如果head不是要找的节点,则取出 p.next 下一个节点继续做 do while{} 循环查找
            	// 如果是 tree 则去 getTreeNode 查找
                if (p instanceof TreeNode)
                	// 如果是 tree 结构,去 TreeNode 中查找对应的 Node
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                	// 否则循环查找 next
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            // 如果查找到了对应节点  matchValue 默认是true则需要对比value相等,如果传入false则不对比value 只要key相等就直接移除
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
            	// 从tree中移除
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                // 这个 else if 判断 要移除的 node 是否是 第一个元素
                else if (node == p)
                	// index是方法开始的时候赋值的 对应key在数组的第几个位置。
                	// 这里是将要移除的链表节点的下一个节点放到对应的index位置作为替换
                    tab[index] = node.next;
                else  
                	// 如果要移除的元素既不是 tree 也不是 head节点 那么将 node.next 前移一位
                    p.next = node.next;
                // 记录的是对 HashMap 的操作次数 上文有提到
                ++modCount;
                // 移除一位以后对应大小 -1
                --size;
                // hashMap 未实现,具体分析 LinkedHashMap 的时候需要分析
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

往红黑树中插入值

  • ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  • 为什么采用红黑树,感觉是因为红黑树虽然查找没有 AVLTree 查找效率高,但是也不像 AVLTree 那样需要频繁调整树的结构来保证平衡。是性能比较均衡的。
        final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            // h 是 要存入的 hash值 k 是 要存入的的key。value 要存入的 value
            Class<?> kc = null;
            boolean searched = false;
            // 先查找到树的根节点  因为是从数组 > 7 到达8 的时候转换为红黑树 ,所以根节点一定是有的 
            TreeNode<K,V> root = (parent != null) ? root() : this;
            // 从根节点向下搜索
            for (TreeNode<K,V> p = root;;) {
                // dir 记录 -1 和 1 位了记录是插入到左子节点还是有子节点
                int dir, ph; K pk;
                // 循环 判断当前要存储的 hash 值是否和节点的 hash 大小
                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 节点返回
                    return p;
                else if ((kc == null &&
                    // comparableClassFor 方法查找节点实现的排序接口
                          (kc = comparableClassFor(k)) == null) ||
                    // compareComparables 是通过排序接口进行两个节点的比较
                         (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;
                // 通过目标 hash 判断出的 dir 是 0 还是 -1 来继续确定往左侧子节点还是右侧 如果确定的子节点 == null 则直接newTreeNode
                // 如果 p = (dir <= 0) ? p.left : p.right != null 的话 需要继续for循环判断下一个 子节点的逻辑,
                // 直到最后 (p = (dir <= 0) ? p.left : p.right) == null 才插入到对应子节点上
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node<K,V> xpn = xp.next;
                    // 创建新 TreeNode
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                    // 通过 dir 判断是左还是右 然后给当前节点的左或者右节点赋值
                    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(tab, hash);

    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();
        // 查找到 Node    通过 (n - 1) & hash 计算出在数组中的位置
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                // 将 Node的数据结构 替换为 TreeNode 的数据结构
                TreeNode<K,V> p = replacementTreeNode(e, null);
                // 通过 do while 循环 每次把 Node 转换成 TreeNode后 做对应节点的关联
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                // 从当前Node开始的 tree
                hd.treeify(tab);
        }
    }

从红黑树中读取操作

  • ((TreeNode<K,V>)first).getTreeNode(hash, key);
        final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
            TreeNode<K,V> p = this;
            do {
                int ph, dir; K pk;
                TreeNode<K,V> pl = p.left, pr = p.right, q;
                // 先确认是 left 还是 right
                if ((ph = p.hash) > h)
                    p = pl;
                else if (ph < h)
                    p = pr;
                // 如果相等则直接返回p
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                // 左子为 null 往右查找
                else if (pl == null)
                    p = pr;
                // 右子为null 往左查找
                else if (pr == null)
                    p = pl;
                // kc 正常传入的是 null
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != 0)
                    p = (dir < 0) ? pl : pr;
                // 通过递归朝着下一个子节点查找
                else if ((q = pr.find(h, k, kc)) != null)
                    //递归找到返回
                    return q;
                else
                    p = pl;
            } while (p != null);
            return null;
        }

HashMap 常被问到的问题

  • getNode() 的时候,(n - 1) & (hash = hash(key)) 为什么可以通过key对应匹配到桶的相对位置

实际上确定数据在桶的位置就是通过 putValue 中有一句代码;所以取数据也是通过这种方式

if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);

  • resize() 的时候 (e.hash & oldCap) == 0 这行代码为什么可以判断扩容后原位置的链表是否需要移动位置(这个问题实际上就是为什么要求数组是2的倍数)

具体原因看图(主要是 n-1 从 0…0111 变为 0…1111)第n+1位是否为1 相当于多一位进行位运算
在这里插入图片描述

  • 什么是哈希碰撞 ,怎么解决的

(什么是哈希函数:https://www.bilibili.com/video/BV1Za411r7LR?spm_id_from=333.999.0.0)
大概思想是:像这样 int out = f(“in”) 一个获取hash的函数,实际上可能的输入 “in” 为无穷多个;但是输出值 out 理论上并不是无穷多,所以一个无穷的输入对比一个有穷尽的返回值来说,一定有不同的 in 对应 相同的 out ;也就是 hash冲突了

  • HashMap 中的hash冲突

Hash冲突有 (开放定址法)(拉链法) 等,HashMap中采用的拉链法,将碰撞的 Node 存储在链表下一位。(所以会发现源码中获取值的时候都是先判断hash 然后判断key的eques方法)

  • 查找的时候 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) 为什么判断了hash还需要判断 key 的值:

因为 Hash冲突 多一个key 可能hash后返回同一个 hash 值。

  • newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 为什么扩容的时候需要乘以DEFAULT_LOAD_FACTOR(0.75)
  • 使用 HashMap 高并发可能会发生什么?

首先并发会把Map对象读取到线程内存

1:如果两个线程同时修改插入数据,且两条数据恰巧发生了碰撞(或者修改了同一个值),如果当前桶位置的Node链表为 A->B 如果两个线程同时修改B的next节点则一定会丢失一个。(特殊情况还有可能使链表形成环,第一个线程修改成 A->B 第二个线程修改成 B->A)
如果发生了环,则有可能会死循环;

2:如果并发的情况下进行了扩容操作:对Node的位置发生了顺序变化,也可能出现环。

  • 为什么不直接将key作为哈希值而是与高16位做异或运算?

目的:为了增加随机性,散列充分,减少碰撞。即使很相近的连个值如 wojiushiwo01 wojiushiwo02 hash之后相差也很大。

  • HashMap加载因子为什么是0.75?

作为一般规则,默认负载因子(0.75)在时间和空间成本上提供了很好的折衷。较高的值会降低空间开销,但提高查找成本(体现在大多数的HashMap类的操作,包括get和put)。设置初始大小时,应该考虑预计的entry数在map及其负载系数,并且尽量减少rehash操作的次数。如果初始容量大于最大条目数除以负载因子,rehash操作将不会发生。
(泊松分布问题…请百度)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值