huangfox

冰冻三尺,非一日之寒!

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

HashMap以<key,value>的方式存放数据,存储在数组中。通过开散列方法解决冲突,数组中存放的Entry作为单向链表的表头。

Entry的源码如下:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;

        //构造、get、set等方法省略

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

    }

  

下面我们通过走读源码来逐步探索HashMap的奥秘!!!

一)“发现问题,存有疑问!”

1)构造HashMap中的capacity

capacity为当前HashMap的Entry数组的大小,为什么Entry数组的大小是2的N次方?

// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
      capacity <<= 1;

 

2)构造HashMap中的loadFactor(装填因子)

threshold = (int)(capacity * loadFactor);

threshold为HashMap的size最大值,注意不是HashMap内部数组的大小。

为什么需要loadFactor,怎么合理的设置loadFactor?

 

3)HashMap的put

     public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);//当key为null时,将其存放在数组的首部:table[0]
        int hash = hash(key.hashCode());
        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;
    }

这里我们关注两个地方:

1)hash

static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

hash的作用是什么?

 

2)indexFor

static int indexFor(int h, int length) {
        return h & (length-1);
    }

indexFor的作用是什么?

 

二)“解惑,拨云见日!”

key的hashCode经过hash后,为了让其在table(table为hashMap的entry[])的范围内,需要再hash一次。这里实际上是采用的“除余法”(h%length)。

源于一个数学规律,就是如果length是2的N次方,那么数h对length的模运算结果等价于a和(length-1)的按位运算,也就是 h%length <=> h&(length-1)

位运算当然比取余效率高,因此这就解释了:为什么Entry数组的大小是2的N次方?

 

我们为什么不直接对key的hashCode进行indexFor运算,还要再hash一次呢?

我做了个简单实验,如下:

int[] hashcode = new int[] { 100000001, 100000011, 100000111,100001111, 100011111, 100111111, 101111111, 111111111 };
for (int c : hashcode)
	System.out.println(hash(c));

数组hashcode中假定了8个hashcode,若对他们除以10取余,余数都为1,全部冲突!

但是通过hash后,对应的值为:

94441116; 94441110; 94441204; 94440071; 94558923; 94592891; 107664244; 117165177

对上面的值除以10取余分别是:6、0、4、1、3、1、4、7,冲突为2。

可见hash能够对hashCode分布更均匀,防止一些蹩脚的hash函数!

从上面我们也可以看出取余的时候,高位的影响比较小,例如:1048592、1048832、1052672、1114112、2097152都可以被16整除(余数为0)!

那么我们得想个办法让高位也要影响到取余的结果,如是便有了Hash。

详情参考:http://marystone.iteye.com/blog/709945

 

对于loadFactor,我也做了个简单实验,如下:

public static void main(String[] args) {
		String s = "a aa aaa b bb bbb c cc ccc d dd ddd e ee eee f ff fff g gg ggg h hh hhh abc bcd cde def efg fgh ghi hij ijk ";
		String[] ss = s.split(" ");
		int size = ss.length;//
		Set<Integer> indexS = new HashSet<Integer>();
		int conflict = 0;
		for (int i = 0; i < ss.length; i++) {
			int index = hash(ss[i].hashCode()) % size;
			if (indexS.contains(index))
				conflict++;
			else
				indexS.add(index);
		}
		System.out.println("冲突数:"+conflict);
	}

当参与取余的除数为size时,冲突数为13

当参与取余的除数为2*size时,冲突数为9

当参与取余的除数为3*size时,冲突数为6

在hashMap中,size受loadFactor的影响。

极端的想,

如果loadFactor很小很小,那么map中的table需要不断的扩容,导致除数越来越大,冲突越来越小!

如果loadFactor很大很大,那么当map中table放满了也不要求扩容,导致冲突越来越多,解决冲突而起的链表越来越长!

 

 

 

 

posted on 2012-07-06 16:08  huangfox  阅读(1308)  评论(1编辑  收藏  举报