HashMap源码分析

package java.util;
import java.io.*;

/**
 * JDK7
 */
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

    /**
     * 默认的初始容量是16,且容量的值必须是2的整数次幂
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 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;

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

    /**
     * An empty table instance to share when the table is not inflated.
     */
    static final Entry<?,?>[] EMPTY_TABLE = {};

    /**
     * entry数组,用来储存数据的
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

    /**
     * 阀值,当size的值大于或等于阀值时,需要调整HashMap的容量(threshold = capacity * load factor)。
     * 		new一个HashMap(new HashMap<K, V>();)后,阀值的大小默认为初始容量的大小,即16
     * The next size value at which to resize (capacity * load factor).
     * @serial
     */
    int threshold;

    /**
     * 加载因子
     * 说明:
     * 		1)加载因子越大,空间利用率越高,但是发生碰撞的几率也会变大,从而导致链表的长度变大,最终造成查找的速度变慢。
     * 		2)加载因子太小,会导致table在扩容的时候还存在很多空闲的空间,造成空间的严重浪费。
     * 		3)一般使用默认的大小0.75即可。
     */
    final float loadFactor;

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;

	
	// **************************** 1.重要的数据结构 ****************************
	
    /**
     * 链表中节点的数据结构,根据节点的数据结构可以看出:该链表是一个单向链表。
     * 说明:
     * 		table中元素的类型是Entry,即:table中的元素是一个单向链表。
     */
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        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 Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
        }

        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) {
        }
    }
	
	// **************************** 重要的数据结构 ****************************
	
	
	// **************************** 2.put、get方法 ****************************
    /**
     * 将key-value对添加到HashMap中,如果key已经存在,则新值替旧值,并返回旧值。
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old value is replaced.
     * 
     * 说明:
     * 		1)当两个entry对象(一个是要新添加进来的对象,一个是已经存在的对象)的key不同,但它们的hash值相同,此时就会发生碰撞。
     * 		2)HashMap使用单向链表来存储元素,当碰撞发生时,HashMap会用传入的key-value来构造一个Entry对象,然后将该Entry放到指定链表的头部。
	 * 		3)我们一般使用String、包装类(Interger等) 作为HashMap的key,原因如下:
	 *			1>String、Interger等包装类是final修饰的类,并且重写了equals()和hashCode()方法。  
	 *			2>计算key的哈希值的前提是:key的值不会发生变化。如果一个key在 调用get(key)方法时的值 和 调用hash(key)方法时的值 不同的话,那么就不能从HashMap中找到正确的entry对象。  
	 *			3>键对象正确地重写equals()和hashCode()方法是非常重要的。hashCode()方法应尽量使两个不相等的key返回不同的hashcode,降低碰撞的几率,这样HashMap的性能就能提高些。  
	 *			4>键的不可变性使得程序能够缓存不同键的hashcode,这将提高整个获取对象的速度,这样HashMap的性能也可提高些。
     */
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
		// 1.如果key为null:
		// 		1.1若map中不存在key为null的键值对,则将该键值对添加到table[0]中的链表的头部,并返回null。
		// 		1.2若map中已存在key为null的键值对,则用新的value取代旧的value,并且返回旧的value。
		// 	注:table[0]中的链表不只是用来存储key为null的键值对,也存储着其它的键值对。
        if (key == null)
            return putForNullKey(value);
				
		// 2.如果key不为null:
		// 		2.1若该key在map中不存在,则计算该key的哈希值,根据key的哈希值找到bucket的位置,然后将key-value对添加到bucket中的链表的头部,并返回null。
		//			注:这里的bucket即Entry数组table的元素,bucket的位置即table的下标。
		// 		2.2若该key在map中已存在,则用新的value取代旧的value,并且返回旧的value。
		// 			注:判断key是否存在的依据是:两个key的哈希值相等,并且key1.equals(key2)
        int hash = hash(key);
        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))) { // 2.2
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i); // 2.1
        return null;
    }
	
    /**
     * Inflates the table.
     */
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize	(根据入参toSize获取一个大于等于toSized的2的整数次幂的数作为数组的容量)
        int capacity = roundUpToPowerOf2(toSize);

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

    /**
     * 将key为null的键值对put到map中。
     */
    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;
    }
	
    /**
     * 获取数组的下标
     * 
     * 说明:
     * 	1)根据key获得一个hashValue[注:hashValue=hash(key)],然后用hashValue对length-1进行按位与运算得到数组的下标,即:hashValue&(length-1)
	 * 	2)数组的length必须是2的整数次幂,原因如下:
     * 		第一:若length是2的整数次幂,则hashValue&(length-1)等价于hashValue%length,那么hashValue&(length-1)同样也实现了均匀散列,但是(位运算)效率会更高。
     * 		第二:若length为奇数,则length-1为偶数,偶数(二进制)的最后一位是0,从而导致hashValue&(length-1)的最后一位永远为0,
     * 			  即:hashValue&(length-1)的结果永远为偶数,最终导致数组中下标为奇数的空间全部被浪费掉。
     */
    static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }
	
	
    /**
     * 将key-value添加到map中,即:使用传入的key-value来构造一个Entry对象,然后将该Entry放到指定链表的头部。
     * 
     * 说明:
     * 	1)如果数组的size已经达到阀值,则先将数组的容量扩展到之前的2倍,然后再将该Entry放到指定链表的头部。
     * 	2)扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新数组中的位置并进行复制处理。
     * 	3)在使用HashMap时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。
     * 
     * Adds a new entry with the specified key, value and hash code to the specified bucket.  
     * It is the responsibility of this method to resize the table if appropriate.
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
	

    /**
     * 获取key对应value
     * Returns the value to which the specified key is mapped, or {@code null} if this map contains no mapping for the key.
     * 
     * 说明:
     * 		1)当两个entry对象的key不同,而它们的hash值相同时(同一个链表中的Entry对象:它们的hash值都相同),就会发生碰撞。
	 *		2)此时,HashMap会使用key1.equals(key2)找到链表中正确的entry对象的value,并返回。
     */
    public V get(Object key) {
        if (key == null) return getForNullKey();
		
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
	
    /**
     * Offloaded version of get() to look up null keys.
     */
    private V getForNullKey() {
        if (size == 0) {
            return null;
        }
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

    /**
     * Returns the entry associated with the specified key in the HashMap. 
     */
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        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 != null && key.equals(k))))
                return e;
        }
        return null;
    }
	
	
	// **************************** put、get方法 ****************************
	
    /**
     * The default threshold of map capacity above which alternative hashing is
     * used for String keys. Alternative hashing reduces the incidence of
     * collisions due to weak hash code calculation for String keys.
     */
    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial capacity and the default load factor (0.75).
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity (16) and the default load factor (0.75).
     */
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }


    /**
     * Rehashes the contents of this map into a new array with a larger capacity. 
     * This method is called automatically when the number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not resize the map, but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current capacity is MAXIMUM_CAPACITY (in which case value is irrelevant).
     */
    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, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

    /**
     * 将当前数组中所有的Entry转移到新的数组中。
     * 
     * 说明:
     * 	在多线程环境下使用HashMap可能会导致死锁的发生:
     * 		1)如果两个线程都发现HashMap需要重新调整大小了,那么很可能出现两个线程同时试着调整数组大小(即:两个线程同时调用transfer方法)的情景,
     * 		2)在这两个线程同时调整数组大小(调用transfer方法)的时候,很可能出现两个Entry互相引用的情况(即产生了循环链表),如果相互引用的情况发生了,那么代码(transfer方法)就进入就死循环了,最直观的表现就是:cpu的利用率极高,几乎接近100%。
	 *		3)排查死锁(死循环):
	 *			1>找到JVM的进程ID(PID):	ps aux | grep java | grep tomcat-advertise
	 *			2>导出JVM当前的线程快照:	jstack -l PID > /data/test/threadDumpFile
	 *			3>找到发生死循环的线程ID:	top -Hp PID		注:cpu利用率极高的那个线程就是发生死循环的线程,该线程ID是用十进制表示的。
	 *			4>将十进制的线程ID转换为十六进制的线程ID:	printf "%x\n" 十进制的ID
	 *			5>在线程快照文件threadDumpFile中找到该线程的堆栈信息,然后根据堆栈信息找到发生死循环的方法。
     */
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

	// ...

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值