JDK1.7 HashMap put方法的详解

什么是Hash


将任意长度的输入,通过hash算法,转变为固定长度的输出,该输出的值就是hash值,器存储数据的结构就是hash表。

hashMap

冲突解决方法

HashMapDectected&Solution

hash表的高低16位取异或&2的N次幂是什么

对hash表中的元素执行高低16位取异或&2的N次幂操作,可以达到以下目的:

  1. 扰乱原始hash值,增加hash表的碰撞性:通过异或和与运算,可以有效地将hash值的高低16位混淆,减少hash冲突可能性,增加hash表的利用率。
  2. 加强hash表的安全性:通过扰乱原始hash值,可以隐藏hash表中元素的真实分布和映射关系,防止通过分析hash值得到的信息进行反向查找或攻击。
  3. 增加hash表元素的私密性:即便获取了hash表及元素的相关信息,也难以通过hash值直接获取元素的高低16位内容,能在一定程度上保护元素的内容安全。
    具体的算法实现如下:
int hash_func(int key) {
    int high = key >> 16;      // 获得高16位
    int low = key & 0xFFFF;    // 获得低16位
    int result = high ^ low;   // 高低16位进行异或
  
    for (int i = 0; i < n; i++) { // n为任意整数
    	result &= 1 << i;  // 与2的i次方相与
        //result &= 1 << (2 * i);  // 与2的n次方相与
    }
  
    return result;  // 返回结果
}

例如,如果key = 0x1234ABCD,n = 3,则:

1. high = 0x1234
2. low = 0xABCD
3. high ^ low = 0xCB91
4. result &= 1 << 0 = 0xCB91
5. result &= 1 << 2 = 0xCB10
6. result &= 1 << 4 = 0xCB00
7. 返回0xCB00

通过异或运算换位和与运算选择掩码,将key的高低16位有效地混淆,达到增加hash表碰撞性、加强安全性和保护元素私密性的目的。


hash表的put方法源码

hashtable没65行的验空。

比较:先遍历找到要存放该map的hsah值,根据hash值找到存储空间,
然后再找该存储空间下的每一个map对象进行比较

如果出现了相同的key或value原来的value(插入在hsah表中的)会被覆盖,原value返回
出现key不同value就会按照头插法插入。

public V put(K key, V value) {
		//判断table是否已经初始化
        if (table == EMPTY_TABLE) {
        	//通过阈值初始化table,解析见2.4.2
            inflateTable(threshold);
        }
        if (key == null)
        	//单独处理key=null的情况,将Entry放在table[0]的位置
        	//相比于其他的key少了计算hash和数组下标的过程
            return putForNullKey(value);
        //通过key的hashcode与一系列hashcode右移后的值进行异或运算
        //目的在于使hashcode的二进制值得每一位都参与到数组下标的计算中去
        int hash = hash(key);
        //通过hash值计算数组下标
        int i = indexFor(hash, table.length);
        //遍历table[i]位置的Entry链表判断是否已有相同key的Entry节点
        //如果有则替换成新的value并返回旧的value
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                //此方法方法体为空,用于子类扩展
                e.recordAccess(this);
                return oldValue;
            }
        }
        //修改次数+1
        modCount++;
        //判断是否需要扩容,使用头插法插入Entry节点,源码见2.4.3
        addEntry(hash, key, value, i);
        return null;
    }

add新元素的方法


void addEntry(int hash, K key, V value, int bucketIndex) {
    // 若当前集合中节点数>=threshold(扩容的临界数量)且当前数组下标有节点,进行数组扩容操作
    if ((size >= threshold) && (null != table[bucketIndex])) {
        // 数组扩容为原来2倍
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    // 添加元素
    createEntry(hash, key, value, bucketIndex);
}
// bucketIndex为数组的下标
void createEntry(int hash, K key, V value, int bucketIndex) {
    // 保存原先bucketIndex下标的Entry对象
    Entry<K,V> e = table[bucketIndex];
    // 采用头插法进行插入,bucketIndex位置插入新的节点,新节点的next指针指向之前的节点
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    // 节点数量+1
    size++;
}

头插的代价最大,所以选头插
1.7多线程扩容可能会形成环状结构,不存在null结点,就会一直插入
map的扩容机制:


HashMap的扩容机制是HashMap在进行put操作时,如果发现容量已达到阈值(默认为当前容量的0.75),就会进行扩容操作。
HashMap的扩容操作主要包含以下步骤:

  1. 新建一个大小为当前大小2倍的数组newTable。
  2. 将当前Entry数组中的所有Entry重新映射到新表中。
  3. 如果某个Entry的hash值在新表的数组索引与旧表不同,该Entry的next引用指向null。
  4. 如果某个Entry的hash值在新表的数组索引与旧表相同,则保留其在新表的相对位置。
    这种扩容机制是实现渐进式rehash的关键:
  5. 新建一个更大的数组,并将数据按新的哈希函数分布在这个数组中。
  6. 旧数组中的数据既可以保持原有的相对位置保存到新数组,也可以根据新的哈希函数选择新位置保存。
  7. 新旧两个数组同时工作,查询操作需要同时查找两个数组。
  8. 写操作只会修改新数组。当所有的Entry都被移至新数组中,旧数组将会被丢弃。
    HashMap的扩容操作有以下好处:
  9. 避免在表的任意时间点上执行昂贵的rehash操作。扩容是渐进的,不会造成查询效率突降。
  10. 新数组的每个桶中的元素数量大致保持在阈值以下,因此查找、添加的时间复杂度仍为O(1)。
  11. 数据渐移,旧数组的数据逐步转移至新数组,实现平滑过渡,不会造成查询不能的窗口期。
  12. 元素散列到新数组的位置有可能发生变化,提高了Hash表防止雪崩效应(大量hash值相同)的能力。
    综上,HashMap的扩容机制采用渐进rehash的策略,保证操作的时间复杂度,实现平滑过渡,增强了HashMap的健壮性。这是HashMap实现高效操作的关键所在。

HashMapSummarise

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值