HashMap原理学习

HashMap

数组和链表两种基本数据类型组成的数据类型。

基本操作

import java.util.HashMap; // 引入 HashMap 类

1、创建hashmap对象:
Map<Key数据类型,Value数据类型> 标识符=new HashMap<Key数据类型,Value数据类型>();

2、主要函数

hashmap.put("Key",Value);//插入键值对为“Key”,Value。
hashmap.get("Key");
hashmap.remove("Key");
hashmap.clear();
hashmap.size();//键值对数量
hashmap.keySet();//只获取Key;
hashmap.values();//只获取Values;

来源于RUNOOB.COM在这里插入图片描述

实现原理

hashmap的默认大小为16,自动扩展或者手动初始化时大小也必须是2的幂次方。理由是:服务于哈希函数,目的是为了相对平均地划分键值对到对应的 index 中。
具体的过程是:每次划分键值对时,是对Key的hashcode数值使用哈希函数,得出index位置。原作者使用的方法是位运算,将Key的hashcode数值与hashmap的大小进行与运算
index=hashcode(Key)&(Length-1);//Length为hashmap大小;
当hashmap大小为16时,hashcode与15(二进制为1111)进行与运算,最后的结果为hashcode(Key)的最后四位二进制数。这样,键值对的index位置就完全由hashcode来决定。与其他大小的hashmap相比,2次幂大小的hashmap似乎更加平均划分键值对。

注意

1、为何hashmap在链表中使用头插法(jdk7及以前),而不用尾插法?
据说是因为原作者觉得后插入的更有可能使用到。但在jdk1.8中已改为尾插法。

2、为什么改成尾插法呢?
为了安全性,防止环化。hashmap在扩容时,是重新创建一个更大的hashmap,然后将旧hashmap中的全部键值对再次哈希函数计算index插入。若是头插法,在多线程的情况下,对旧hashmap的键值对再次哈希计算时可能会导致环化。
简单来说,若线程1在读取旧hashmap中的一个链表的第一个元素时,记录下了第一个元素的Key,Value和next。这时CPU调度线程2来运行,线程2将第一个元素哈希到新hashmap中,再将第二个元素哈希到新hashmap时,若采用头插法,会将第一个元素移到第二个元素的next位置上,但是线程1记下的顺序是第一个元素的next是第二个元素。这时候就出现了1.next=2; 2.next=1;的环化情况。

2、高并发中,hashmap可能会出现死循环。
原因:见问题2. 环化的hashmap链表在读取时会陷入死循环。

3、多线程访问hashmap是不安全的。
原因:hashmap不支持线程同步。在hashmap源码中没有对get、put操作加同步锁。

4、java8中,hashmap得到了怎样的优化?
(1)java8以前hashmap采用数组+单链表的形式存储,java8在采用数组+单链表的同时,当单链表中的元素数量超过8个时改用红黑树存储,将时间复杂度由O(n)降低到O(logn)。
(2)java7使用头插法;java8使用尾插法。
(3)java7在rehash扩容时会重新计算key的哈希值进行&操作;java8在rehash时不会再计算key的哈希值(我觉得奇怪,不重新计算的话,那是有存储之前计算过的哈希值吗?),直接与容量进行&操作,得到index。

疑惑

?????????????????
1.7中是通过更改hashSeed值修改节点的hash值从而达到rehash时的链表分散,而1.8中键的hash值不会改变,rehash时根据(hash&oldCap)==0将链表分散。
?????????????????
有一说,是扩容时已经存在的元素不用再进行rehash计算,直接通过:新的hash=原来的index+原来的table大小,计算出扩容后的hash值进而计算新的index。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMap的底层是由一个哈希桶数组组成的。而哈希桶的大小取决于初始化HashMap时指定的初始容量,即bucket数组的大小。比如,如果使用new HashMap(19),那么哈希桶数组的大小将为32,即2^5。 HashMap在第一次进行put操作时才会分配内存并初始化哈希桶数组。当put操作需要添加元素时,HashMap会根据元素的哈希值计算它在哈希桶数组中的位置,并将元素放入对应的位置。 HashMap在负载因子达到0.75f时会进行扩容,即当哈希桶数组中的元素个数达到了容量的75%时会自动扩容。扩容过程会重新计算元素在新的哈希桶中的位置,并将元素重新插入。 当两个对象的哈希值相同时,会发生哈希冲突或碰撞。这意味着它们会被放置在哈希桶数组的同一个位置上的链表中。在这种情况下,HashMap会从对应位置的链表中遍历查找并比较键的值,以获取正确的值对象。 重新调整HashMap大小时存在一个问题,即重新哈希。因为在扩容时,HashMap需要重新计算元素的哈希值,并将元素插入到新的哈希桶数组中。这个过程会导致性能的损耗,并且可能会导致哈希冲突的增加。 另外值得一提的是,HashSet的底层也是用HashMap实现的,它只存储key,其中的val都是Object对象。因此,HashSet也具有哈希桶的特性。您可以通过学习JavaHashMap的源码来深入了解哈希桶的实现原理

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值