hashmap源码学习,如何查找索引,解决冲突
之前看过一部分jdk的源码,发现看了之后又忘了,或者是看的不够深入,有次去面试:问我hashmap怎么实现的,结果我答的不怎么样,没答出一些关键部分,现在再重新开读读jdk的util包下的一些源代码:
特别声明:小弟发博客纯属学习,若有错误,不当之处请指出!!!
首先,我们大家都知道hashmap内部用的是散列实现,但是具体怎么实现的呢?如果解决冲突的呢?
实现原理:
hashmap用数组实现,数组中存放的是Entry类型的元素,每个Entry元素其实是一个key-value对,持有下一个元素的引用,这就说明table数组中的Entry元素还是某个Entry链表的首节点,指向该链表的下一个元素,所以hashmap用“数组链表”实现的;
这个是网上找的hashmap底层数据结构的一个图片:
1、初始化
public HashMap(int initialCapacity, float loadFactor) { // 有两个参数,一个初始值,一个加载因子,默认情况初始值是16,加载因子是0.75 ........ int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); }
2、扩容(重构)
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
3、解决冲突:在数据结构中有线性散列,平方散列等等方式:
hashmap源码采用的方式是:通过复杂的算法找到要插入的位置如下,如果已经有元素了,就线性的往后查找
hash值的算法:
static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
在数据结构教材上是直接除以一个数取余,而Java实现却如此复杂,网上查资料:
先看个例子,一个十进制数32768(二进制1000 0000 0000 0000),经过上述公式运算之后的结果是35080(二进制1000 1001 0000 1000)。看出来了吗?或许这样还看不出什么,再举个数字61440(二进制1111 0000 0000 0000),运算结果是65263(二进制1111 1110 1110 1111),现在应该很明显了,它的目的是让“1”变的均匀一点,散列的本意就是要尽量均匀分布。
将得到的值和(length-1)相与,得到的结果就是插入的索引位置
static int indexFor(int h, int length) { return h & (length-1); }
具体的插入方法:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); 先得到hash值 int i = indexFor(hash, table.length); 得到插入的位置 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; } } modCount++; addEntry(hash, key, value, i); 该位置为空的时候就插入 return null; }
到此就解决了刚刚提出的问题:如果得到索引,如何解决冲突。