加入收藏 | 设为首页 | 会员中心 | 我要投稿 汽车网 (https://www.0577qiche.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

Java 中的 HashMap

发布时间:2023-04-17 12:22:26 所属栏目:教程 来源:
导读:Java 中的 HashMap 作者:Grey 原文地址:Java 中的 HashMap 扩容机制 jdk1.7 先生成新数组。 遍历老数组中的每个位置上的链表上的个元素。 取个元素的key,并基于新

Java 中的 HashMap
作者:Grey

原文地址:
Java 中的 HashMap 作者:Grey 原文地址:Java 中的 HashMap 扩容机制 jdk1.7 先生成新数组。 遍历老数组中的每个位置上的链表上的个元素。 取个元素的key,并基于新

Java 中的 HashMap
作者:Grey

原文地址:Java 中的 HashMap

扩容机制
jdk1.7
先生成新数组。

遍历老数组中的每个位置上的链表上的个元素。

取个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标。

将元素添加到新数组中去。

所有元转移完了之后,将新数组赋给HashMap对象的table属性。

jdk1.8
同样先生成新数组。

遍历老数组中的每个位置上的链表或红黑树。

如果是链表,则直接将表中的每个元索重新计算下标,并添加到新数组中去。

如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元索对应在新数组中的下标位置。

统计每个下标位置的元索个数。
如果该位下的元素个数超过了8,则生成一个新的红黑树,开将根节点添加到新数组的对应位置。
如果该位置下的元素个数没有超过8,那么则生成一个链表,开将链表头节点添加数组的对应位置。
所有元素转移完了之后,将新数组赋给HashMap对象的table属性。

为什么要使用红黑树
因为在hash冲突比较频繁的情况下,生成的链表长度会非常长,这样就会导致查询的效率会大大降低,为了解决链表过长,查询效率过低的问题,所以使用红黑树来优化,有一个阈值8,超过了这个阈值,就会使用红黑树。

jdk1.7中HashMap死循环问题
复现代码如下,注:以下代码需要指定jdk1.7为运行环境

import java.util.HashMap;
import java.util.Map;
public class HashMapMultiThread {
    static Map<String, String> map = new HashMap<>();
    public static class AddThread implements Runnable {
        int start = 0;
        public AddThread(int start) {
            this.start = start;
        }
        @Override
        public void run() {
            for (int i = start; i < 100000; i += 2) {
                map.put(Integer.toString(i), Integer.toBinaryString(i));
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new HashMapMultiThread.AddThread(0));
        Thread t2 = new Thread(new HashMapMultiThread.AddThread(1));
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(map.size());
    }
}
运行main方法,发现程序出现死循环,无法停止。这个问题出现在HashMap扩容操作调用的transfer方法中,

    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
假设HashMap当前状态如下

image

现在有两个线程A和线程B都要执行put操作,线程A和线程B都会看到上面图的状态快照

线程A执行到transfer函数中

Entry<K,V> next = e.next;
这行代码时,此时在线程A的栈中

e = 4节点
next = 5节点
假设此时线程B正在执行transfer函数中的while循环,即会把原来的table变成新一table(线程B自己的栈中),再写入到内存中。如下图(假设两个元素在新的hash函数下也会映射到同一个位置)

image

线程A继续执行(看到的仍是旧表),即从transfer代码

Entry<K,V> next = e.next;
处接着执行,当前的

e = 4号节点, 
next = 5号节点

(编辑:汽车网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章