基于jdk1.7的hashMap的学习笔记

一、什么是HashMap

HashMap是一个用Key-Value键值对存储数据的集合,每一个键值对也称为Entry,这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。HashMap数组每一个元素的初始值都是Null。

HashMap 实现了Map所有的操作,允许null作为 key/value;无序(因为h&length-1,也就是bucket数组索引无序)

HashMap 除了非同步性,k\v 允许null, HashTable与之相反,为线程安全(但效率不如ConcurrentHashMap),key与value都不允许null值。

两个因素影响HashMap性能:”initial capacity”、”load factor” . threshold=(capacity * load factor),当size超过threshold,会产生rehash


1、put方法原理

   当我们调用put方法时,比如HashMap.put("apple",0),插入一个key为apple的元素,这是要调用一个希哈函数来确定Entry的插入位置(index)。

    因为HashMap的长度是有限的,当插入的Entry越来越多时,再完美的Hash函数也难免会出现index冲突的情况。这个时候HashMap利用链表来解决冲突。HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。当新来的Entry映射到冲突的数组位置时,只需要插入到对应的链表即可。注意,这里的插入使用的是头插法,原因是因为HashMap的发明者认为,后插入的Entry被查找的可能性更大

2、HashMap的默认初始长度

    HashMap的默认初始长度capacity为16,并且每一次扩容时长度必须为2的幂次。原因是为了服务于key映射到index的算法。从Key映射到HashMap数组的对应位置,会用到一个Hash函数:

index =  Hash(“apple”)

为了实现高效率的算法,HashMap的作者使用了位运算的方法。

index = HashCode(key) & (length-1)

下面我们以值为“book”的Key来演示整个过程:


1.计算book的hashcode,结果为十进制的3029737,二进制的101110001110101110 1001。


2.假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。


3.把以上两个结果做与运算101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9。

capacity 必须为2的n次幂,则length-1肯定为奇数,在位运算h & (length-1)唯一性更高,减少

了collision的发生,也就是保证bucketIndex低重复性.,如果使用如10为长度时,不同的HashCode运算结果如下:


HashCode:101110001110101110 1001

HashCode:101110001110101110 1111

 length-1   :                                    1001

 Index       : 1001

 Index       : 1001


这样的话冲突几率就提高,并且一些index不会出现(0111),这样就不符合Hash算法均匀分布的原则。所有只有Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。


3、高并发下的HashMap

刚刚说过HashMap是线程不安全的。ReHash在并发的情况下可能会形成链表环。

首先要先理解HashMap的resize。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高。这时候,HashMap需要扩展它的长度,也就是进行Resize

影响发生Resize的因素有两个:


1.Capacity

HashMap的当前长度。上一期曾经说过,HashMap的长度是2的幂。


2.LoadFactor

HashMap负载因子,默认值为0.75f。


衡量HashMap是否进行Resize的条件如下:

HashMap.Size   >=  Capacity * LoadFactor


这个过程经历两个步骤:

1、扩容

创建一个新的数组,长度为原来的两倍;

2、ReHash

遍历原Entry数组,把所有的Entry重新Hash到新数组。此时的Hash规则已经改变,因为length发生了改变。

Rehash的代码如下:

/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;//遍历这个hash table(桶数组)
for (Entry<K,V> e : table) {//遍历数组中的链表结构
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {//hashSeed变化,需要重新执行hash函数
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
       //下面的操作可能引发链表环
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
 

在高并发下我们通常使用CurrentHashMap。

1.8中对链表进行了优化,如果链表上的数据结点超过了8个就会将链表转换为红黑树,提高查询效率

并且在计算HashCode时的异或次数降到一次




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值