HashMap源码分析及冲突处理的细节

一.  首先看一下hashmap的数据结构,可以看到是数组加链表实现的。

transient Entry<K,V>[] table =(Entry<K,V>[]) EMPTY_TABLE;

可以看到它的实现是一个Entry<K,V>类型的名为table的数组。而Entry是HashMap中的一个内部类。

static class Entry<K,V> implementsMap.Entry<K,V> {

final K key;

V value;

Entry<K,V> next;

int hash;

它有四个属性,key,value,next,hash。由于有next属性,所以自然会想到链表的结点类,事实上,当出现hash冲突时,由于HashMap使用链地址法来解决冲突。所以table数组的每一个元素就会形成链表结构。所以可以说HashMap就是一个存储链表的数组。

二.   HashMap的table数组的默认大小是16,并且大小永远是2的n次方。它还有一个负载因子,默认为0.75,可以通过带参数的构造方法自己指定。负载因子loadFactor的作用是:HashMap中的实际的数据大小除以总容量(initialCapacity),当值达到loadFactor时,HashMap的总容量自动扩展一倍。

staticfinal int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

static final float DEFAULT_LOAD_FACTOR = 0.75f;

计算threshold,值为capacity *loadFactor。

threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY +1);

这里就会判断,当size的值大于threshold(即capacity *loadFactor)时,就会进行扩容。

if ((size >= threshold) && (null != table[bucketIndex])){

resize(2 * table.length);

三.接下来以put方法作为入口,进行分析。

1.首先进行hash运算,并求出将要存入的数组下标。

int hash = hash(key);

int i = indexFor(hash, table.length);

接下来看看计算下标的算法是如何实现的。进入到indexFor方法中,实现的代码如下:

static int indexFor(int h, int length) {

// assertInteger.bitCount(length) == 1 : "length must be a non-zero power of2";

return h &(length-1);

}

具体是h &(length-1),这样计算的值介于0和length-1之间,有点类似于hash%length 的求模运算。之所以用&运算我认为是位运算的效率更高吧。

2.然后是下面这段代码:

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);

returnoldValue;

}

}

modCount++;

addEntry(hash, key,value, i);

会判断table[i]是否为null,这是会出现两种情况,先分析第一种情况,即table[i]还没有元素,是null的情况,这时循环就没有执行,继续往下,去执行addEntry方法。addEntry方法中先进行判断是否需要扩容,如果需要,就进行扩容。然后又进入到createEntry方法中。它的代码实现如下:

void createEntry(int hash, K key, V value, int bucketIndex) {

Entry<K,V> e =table[bucketIndex];

table[bucketIndex] =new Entry<>(hash, key, value, e);

size++;

}

它做的工作就是把hash,key, value, e四个属性组装成一个Entry的对象e,并将它放在数组下标相应的位置,这时如果加入的是第一个元素,e则为null,所以next指向了null。最后再把size加1.

下面分析第二种情况,即即table[i]已经有了元素,不是null的情况。这时会执行上面的那一段for循环,这个循环的作用就是依次遍历整个table[i]链表,并且判断这个链表的每一个元素的key是否和新加进来的元素的key相同,如果相同新的value就会覆盖旧的value,即保证HashMap中唯一的key有唯一的value.

进行完了覆盖的操作后,就会执行剩下的代码,和第一种情况一样,执行addEntry方法。addEntry方法中先进行判断是否需要扩容,如果需要,就进行扩容。再执行createEntry方法。这时e = table[bucketIndex];计算出来的e就不为null了,为原来的i下标处的元素。然后又封装一个新的Entry对象,放入到table[i]位置,它的next指向了e,即原来的table[i]处的元素。

所以通过分析我们可以发现,最后放入的元素总是在这个冲突链表的表头的位置。

最后,可以看到,当出现冲突时,会把数据放入链表中,每次插入新的元素都会对整个链表进行遍历操作,影响程序的效率。所以当我们向HasnMap中放入的key的数据类型是自定义类型的时候,要按照规范合理的实现hashcode和equals方法,尽量避免冲突。另外,由于它的底层实现也是数组,所以也要尽量避免扩容。最好能估算出初始的大小,而对于负载因子,据说0.75是计算出的最佳值,所以还是用默认的吧。

时间: 2024-10-03 16:37:25

HashMap源码分析及冲突处理的细节的相关文章

[Java] HashMap源码分析

1.概述 Hashmap继承于AbstractMap,实现了Map.Cloneable.java.io.Serializable接口.它的key.value都可以为null,映射不是有序的. Hashmap不是同步的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Collections.synchronizedMap(new HashMap()); (除了不同步和允许使用 null 之

【JAVA集合】HashMap源码分析(转载)

原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储的对象是一个键值对对象(Entry<K,V>): HashMap补充说明 基于数组和链表实现,内部维护着一个数组table,该数组保存着每个链表的表头结点:查找时,先通过hash函数计算hash值,再根据hash值计算数组索引,然后根据索引找到链表表头结点,然后遍历查找该链表: HashMap数据

HashMap源码分析(转载)

一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同.)此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Coll

Java中HashMap源码分析

一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtable大致相同)此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Collections.sync

Java进阶之----HashMap源码分析

今天我们接着来看HashMap的源码,对几个常用的方法进行分析.在分析之前,我们还是要先对HashMap的结构有一个了解.看过之前我分析的ArrayList和LinkedList源码的朋友应该清楚,ArrayList内部是以数组实现的,LinkedList内部是以链表实现的.而HashMap则是对数组和链表的结合,虽然看上去复杂了一些,不过仔细分析一下,还是很好理解的.我们来看一张图片,是我根据我的理解画的. 我们在来看看Entry的内部结构是什么: 以上两个图,相信大家对HashMap的结构有

Java集合系列之HashMap源码分析

一.HashMap简介 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能. ps:本文中的源码来自jdk1.8.0_45/src. 1.重要参数 HashMap的实例有两个参数影响其性能. 初始容量:哈希表中桶的数量 加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度 当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的

Java集合之HashMap源码分析

一.HashMap简介 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能. ps:本文中的源码来自jdk1.8.0_45/src. 1.重要参数 HashMap的实例有两个参数影响其性能. 初始容量:哈希表中桶的数量 加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度 当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的

Java BAT大型公司面试必考技能视频-1.HashMap源码分析与实现

视频通过以下四个方面介绍了HASHMAP的内容 一. 什么是HashMap Hash散列将一个任意的长度通过某种算法(Hash函数算法)转换成一个固定的值. MAP:地图 x,y 存储 总结:通过HASH出来的值,然后通过值定位到这个MAP,然后value存储到这个MAP中的HASHMAP基本原理 1. KEY 是否可以为空?可以,Null当成一个Key来存储 2. 如果Hash KEY重复了会覆盖吗?会覆盖,但返回旧的值 3. HASHMAP什么时候做扩容?put 的时候,阀值高于或等于0.7

HashMap源码分析--jdk1.8

JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8 HashMap概述 ??1. HashMap是可以动态扩容的数组,基于数组.链表.红黑树实现的集合.??2. HashMap支持键值对取值.克隆.序列化,元素无序,key不可重复value可重复,都可为null.??3. HashMap初始默认长度16,超出扩容2倍,填充因子0.75f.??4.HashMap当链表的长度大于8的且数组大小大于64时,链表结构