hashMap

1.hashMap的数据结构

hashMap底层采用数组+链表的形式存放数据(JDK1.8之后,引入了红黑树)。当存一个数据时,首先使用hashCode()方法计算这个数据的hash值,hash值确定这个数据存放在数组的哪一个位置,也就是我们所说的哪一个bucket中。确定存放的bucket之后,如果存入的值不等于当前bucket的值,则以链表的形式存放在链表的末尾,当链表的长度超出了一定长度之后,在jdk1.8之后,如果链表超出了一定长度(默认6),出于对性能的考虑,则以红黑树的格式存储数据。

2.关于hashMap的扩容

hashMap的有个默认的负载因子,当存入数据时,如果当前的负载因子大于0.75,则需要对当前的hashmap进行扩容。扩容是一个很耗费时间的过程,因为需要将每个bucket中的数值取出,进行重新计算,从而放入扩容后的hashMap中。因此,需要在声明hashmap的时候,设定初始容量,在阿里巴巴的规范文旦中给出了对于初始容量的描述:

hashmap底层的计算多以位进行计算,因此将初始长度设置成2的指数最为合理;

3.put方法

put方法内部涉及比较多的逻辑,具体的过程如下图所示:

4.get方法

(1)指定key 通过hash函数得到key的hash值
int hash=key.hashCode();

(2)调用内部方法 getNode(),得到桶号(一般为hash值对桶数求模)
int index =hash%Entry[].length;
jdk1.6版本后使用位运算替代模运算,int index=hash&( Entry[].length - 1);

(3)比较桶的内部元素是否与key相等,若都不相等,则没有找到。相等,则取出相等记录的value。

(4)如果得到 key 所在的桶的头结点恰好是红黑树节点,就调用红黑树节点的 getTreeNode() 方法,否则就遍历链表节点。getTreeNode 方法使通过调用树形节点的 find()方法进行查找。由于之前添加时已经保证这个树是有序的,因此查找时基本就是折半查找,效率很高。

(5)如果对比节点的哈希值和要查找的哈希值相等,就会判断 key 是否相等,相等就直接返回;不相等就从子树中递归查找。

HashMap中直接地址用hash函数生成;解决冲突,用比较函数解决。如果每个桶内部只有一个元素,那么查找的时候只有一次比较。当许多桶内没有值时,许多查询就会更快了(指查不到的时候)。

 

5.解决hash冲突的常见方法

(1). 开放定址法:即发生冲突时,去寻找下一个空的哈希地址。只要哈希表足够大,总能找到空的哈希地址。

(2)再哈希法:即发生冲突时,由其他的函数再计算一次哈希值。

(3)建立公共溢出区:将哈希表分为基本表和溢出表,发生冲突时,将冲突的元素放入溢出表。

(4) 链地址法:将哈希表的每个单元作为链表的头结点,所有哈希地址为 i 的元素构成一个同义词链表。即发生冲突时就把该关键字链在以该单元为头结点的链表的尾部。

HashMap 就是使用链地址法来解决冲突的(jdk8中采用平衡树来替代链表存储冲突的元素,但hash() 方法原理相同)。数组中的每一个单元都会指向一个链表,如果发生冲突,就将 put 进来的 K- V 插入到链表的尾部。

6.总结

(1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。

(2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。

(3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。

(4) JDK1.8引入红黑树大程度优化了HashMap的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值