hashmap源码学习,如何查找索引,解决冲突

之前看过一部分jdk的源码,发现看了之后又忘了,或者是看的不够深入,有次去面试:问我hashmap怎么实现的,结果我答的不怎么样,没答出一些关键部分,现在再重新开读读jdk的util包下的一些源代码:

特别声明:小弟发博客纯属学习,若有错误,不当之处请指出!!!

 

首先,我们大家都知道hashmap内部用的是散列实现,但是具体怎么实现的呢?如果解决冲突的呢?

实现原理:

hashmap用数组实现,数组中存放的是Entry类型的元素,每个Entry元素其实是一个key-value对,持有下一个元素的引用,这就说明table数组中的Entry元素还是某个Entry链表的首节点,指向该链表的下一个元素,所以hashmap用“数组链表”实现的;

这个是网上找的hashmap底层数据结构的一个图片:

1、初始化

View Code
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。

   那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过threshold(数组大小*loadFactor)时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

 

 

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

  

到此就解决了刚刚提出的问题:如果得到索引,如何解决冲突。

posted @ 2012-09-13 22:17  softwa  阅读(571)  评论(0编辑  收藏  举报