HashMap 源码分析

/**
     * The default initial capacity - MUST be a power of two.
     * 默认的初始容量必须是2的几次方。
     */
    static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     * 最大的容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     * 负载因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry[] table;


下面来看看get(Object key)方法的流程以及源码

public V get(Object key) {
	        if (key == null)
	            return getForNullKey();
	        int hash = hash(key.hashCode());
	        for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null; e = e.next) {
	            Object k;
	            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
	                return e.value;
	        }
	        return null;
	    }
	 
	 //
	 private V getForNullKey() {
	        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
	            if (e.key == null)
	                return e.value;
	        }
	        return null;
	    }
	 
	 //计算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值与数组长度按位与算出元素在数组中的位置
	 static int indexFor(int h, int length) {
	        return h & (length-1);
	    }

      从上面源码中可以看出:当系统决定存储 HashMap 中的 key-value 对时,是没有考虑 Entry 中的value的,仅仅只是根据key 来计算并决定每个Entry 的存储位置。这也说明了前面的结论:我们完全可以把Map集合中的value 当成key的附属,当系统决定了key 的存储位置之后,value 随之保存在那里即可。

static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    int hash = hash(key.hashCode());这句来调用的上面的方法,可以看到hash(int h)方法里面纯粹的数学计算。对于给的对象,

hashCode()的值是相同的,那么算出的hash码也会是相同的。

     接下来调用了此方法

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

      通过h &(table.length -1) 来得到该对象的保存位置,通过前面的定义可以知道,HashMap 底层数组的长度总是2的n次方,经过N次推算可以得出hash值与偶数与,得到的相同的值的几率比奇数大,如果相同的值多了,数据便会分配到相同的数组的位置上,链表中的值就多了,从链表中查询效率也就比较低了。

     而hash值与奇数与,得到相同的值的几率低,便会分配不同的数组的值,查询只需要一次就好,效率相对来说也就比较高。

     下面给出例子

      一个哈希值是8,二进制是1000,一个哈希值是9,二进制是1001。和1111(奇数)与运算后,分别还是1000和1001,它们被分配在了数组的不同位置,这样,哈希的分布非常均匀。

      再和1110(偶数)与,分别得到的值是1000和1000,得到了相同的值,哈希值8和9的元素多被存储在数组同一个位置的链表中。链表中的值越多,操作时对链表循环越多,效率也就越低了。所以,一定要哈希均匀分布,尽量减少哈希冲突,减少了哈希冲突,就减少了链表循环,就提高了效率。

 

   下面这张图显示了HashMap的存储方式,数组+链表。


 下面来看看put(K key, V value)方法,了解整个流程及源码分析

public V put(K key, V value) {
	        if (key == null)
	            return putForNullKey(value);
	        int hash = hash(key.hashCode());
	        int i = indexFor(hash, table.length);
	        
	        //遍历数组此位置的链表,如果有和key相同的,则覆盖该值,返回旧值
	        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++;
	        //如果for循环里面没有找到,新增一个。
	        addEntry(hash, key, value, i);
	        return null;
	    }
	 
	 
	 //当K为空的时候调用此方法
	 private V putForNullKey(V value) {
		    //遍历,有相同的值的变覆盖,返回旧值。
	        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
	            if (e.key == null) {
	                V oldValue = e.value;
	                e.value = value;
	                e.recordAccess(this);
	                return oldValue;
	            }
	        }
	        modCount++;
	        //没有就新增
	        addEntry(0, null, value, 0);
	        return null;
	    }

//上面新增时调用的方法
	void addEntry(int hash, K key, V value, int bucketIndex) {
		Entry<K,V> e = table[bucketIndex];
	        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
	        
	        //扩容,2倍table的长度。
	        //注意threshold = (int)(capacity * loadFactor);
	        if (size++ >= threshold)
	        	//调用下面的方法
	            resize(2 * table.length);
	    }
	
	void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        //调用下面的方法
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }
	
	//tranfer方法将所有的元素重新哈希,因为新的容量变大,所以每个元素的哈希值和位置都是不一样的。
	void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

    如果数据大小是固定的,那么最好给HashMap设定一个合理的容量值。不然不停的扩容,不停的给所有的元素重新哈希,那效率就惨了。

//通过K删除元素,由上面的图可以看到,遍历数组元素上的链表
	final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);
        
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;
        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

HashMap就到这了,主要的几个方法都简略的分析了下,把HashMap的结构弄清楚了,其余的方法步骤都差不多。

源码中可以看到HashMap不是同步的


转自: http://blog.csdn.net/zl3450341/article/details/6036144

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值