码迷,mamicode.com
首页 > 其他好文 > 详细

HashMap源码分析

时间:2015-05-27 14:05:42      阅读:175      评论:0      收藏:0      [点我收藏+]

标签:hashmap   源码   entry   bucket   

------------------------------------------------------
重要的数据结构
------------------------------------------------------
	// ******* 这个类是HashMap的一个内部类
	// ******* 这个entry本质上就是一个单向链表
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next; // 正因为有这个成员变量,才使entry成为一个单向链表
        final int hash;
		// 构造方法需要传入的参数有4个:hash值h、Key值k、Value值v、下一个entry节点n
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
		
		// 判断两个entry是否相等,当它们的Key和Value都相等时返回true
        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());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that's already
         * in the HashMap.
         */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }
	
--------------------
	
// HashMap的结构(这里只写了它的属性,省略了它的方法)
public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{
	// The default initial capacity - MUST be a power of two.
	// 默认的初始容量(entry数组的长度)是16,并且值必须是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.
    // 最大的容量,当传入的容量过大时将使用这个值,并且必须是2的整数次幂且小于2的30次方
    static final int MAXIMUM_CAPACITY = 1 << 30;

	The load factor used when none specified in constructor.
	// 默认的加载因子是0.75
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

	// The table, resized as necessary. Length MUST Always be a power of two.
	// (储存数据的)entry数组,长度必须是2的整数次幂
    transient Entry[] table;

	// The number of key-value mappings contained in this map.
	// HashMap(即:entry数组)中已存在的键值对的数量
    transient int size;

	// 阀值,当size的值大于或等于阀值时,需要调整HashMap的容量(threshold = capacity * load factor)
    int threshold;

	// The load factor for the hash table.
	// 加载因子的大小
	// 说明:加载因子越大,对空间的利用越充分,但是查找的效率会降低(链表长度会越来越长);
	//		 加载因子太小,那么表中的数据将过于稀疏(很多空间还没用,就开始扩容了),对空间造成严重浪费。
	//		 系统默认加载因子为0.75,这是一个比较理想的值,一般情况下我们是无需修改的。
    final float loadFactor;

	// HashMap被改变的次数
    transient int modCount;
}
------------------------------------------------------
存储对象相关的方法:
------------------------------------------------------

    // 将key-value对添加到HashMap中,如果已存在,则新值替旧值,并返回旧值
    public V put(K key, V value) {    
        // 1 若key为null,则将该键值对添加到table[0]中。    
        if (key == null)    
            return putForNullKey(value);    
        // 2 若key不为null,则计算该key的哈希值,然后将key-value对添加到该哈希值对应的链表(Entry)中。    
        int hash = hash(key.hashCode());    
        int i = indexFor(hash, table.length);
		// 2.1 若该key对应的键值对已经存在,则用新的value取代旧的value;返回了旧的value后,退出!
        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;    
            }    
        }    
   
        // 2.2若该key对应的键值对不存在,则将key-value对添加到table中
		//(注:transient Entry[] table;// table是一个Entry数组,查看Entry的源码,会发现:Entry实质上是一个链表)
        modCount++;  
        //将key-value添加到table[i]处,返回null后,退出。
        addEntry(hash, key, value, i);    
        return null;    
    }

--------------------

	// putForNullKey()的作用是将key为null的键值对添加到table[0]位置
    private V putForNullKey(V value) {
		// 若已经存在key为null的键值对,则替换并返回旧值
        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;
            }
        }
		// 如果不存在key为null的键值对,则直接添加到table[0]处!
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
	
--------------------

	// 根据传入的hash值算出entry数组对应的下标,即:hash算法的实现
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
	
	说明:
		1,	除留余数法(取模法):用hash值除以关键字后得到的余数为哈希地址,即用hash值对关键字取模(MOD)的结果为哈希地址。
			这是一种最简单,也是最常用的构造哈希函数的方法,Hashtable就是采用这种方法实现的(用hash值对entry数组的length取模),这种方法基本能保证元素在哈希表中散列的比较均匀,但取模会用到除法运算,效率很低。
			除留余数法对关键字的选取很重要,由经验得知:一般情况下,选关键字为质数或不包含小于20的质因数的合数。

		2,	HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多。
			哈希表的容量(entry数组的length)必须是2的整数次幂的原因:
				首先,length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率;
				其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于hash值),即:与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性。
				如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间。
				
--------------------

	// 添加一个entry
    void addEntry(int hash, K key, V value, int bucketIndex) {
		// table[bucketIndex]即put(K key, V value)方法中的table[i],这里只是把下标的变量名由i换成bucketIndex罢了
		
		// ******* 把已有的链表备份一下
        Entry<K,V> e = table[bucketIndex]; 
		// 把新添加进来的entry放到链表的头部,即:把旧的链表(Entry)赋给新添加进来的Entry的 Entry<K,V> next;字段
        table[bucketIndex] = new Entry<>(hash, key, value, e);
		// 如果entry数组的实际大小(已放入entry的数量) 大于或等于阈值threshold,则
		// 将会创建一个是原来entry数组(即table)大小两倍的entry数组,并将原来的entry对象放入新的entry数组中。
        if (size++ >= threshold)
            resize(2 * table.length);
    }
	
--------------------

	// 重新调整entry数组的length,并将原来的entry对象放入新的entry数组中
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
		// 新建一个entry数组
        Entry[] newTable = new Entry[newCapacity];
		// 将原来的entry对象放入新的entry数组中
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }
	说明:	扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。
			因此,我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。
			
--------------------

	// 将原来的entry对象放入新的entry数组中
    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);
            }
        }
    }

------------------------------------------------------
获取对象相关的方法:
------------------------------------------------------

	// 获取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;
			// 判断key是否相等。注:如果hash值相同,就调用key.equals()方法比较
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
		// 没有找的则返回null
        return null;
    }
	
--------------------

	// 获取key为null的对象的值
	// HashMap将key为null的元素存储在table[0]位置,但不一定是该链表的第一个位置,因为有可能用其他key的hash值算出的bucket位置也为0!  
    private V getForNullKey() {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }





HashMap源码分析

标签:hashmap   源码   entry   bucket   

原文地址:http://blog.csdn.net/wodewutai17quiet/article/details/46044311

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!