天天看点

Java 1.7 HashMap源码阅读

目录

    • 1. 构造方法
    • 2. put方法
    • 3. resize扩容方法
    • 4. remove
    • 5. initHashSeedAsNeeded 和 hash
    • 6. 多线程 resize 线程不安全演示

1. 构造方法

// 默认Hash 
	// The default initial capacity - MUST be a power of two.
	// 默认容量即hash可放置的元素数量
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
	// The load factor used when none specified in constructor.
	// 未指定时的默认扩容因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
 	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 的注释
        // If table == EMPTY_TABLE then this is the initial capacity at which the
    	// table will be created when inflated.
    	// 如果表为空表则threshold将在inflated时被创建
        threshold = initialCapacity;
        // init 为空方法 即当真正向hashmap中put时使用inflateTable方法初始化Entry数组
        init();
    }
           

2. put方法

/**
     * 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.
     * 
     * 
     */
 public V put(K key, V value) {
 		
        if (table == EMPTY_TABLE) {
       		 // 真正的初始化方法
        	// 代码比较简单 
        	// threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); 
        	// threshold 初始时为12
            // table = new Entry[capacity];
            //  最后单独说initHashSeedAsNeeded(capacity);
            inflateTable(threshold); // 这里传的是初始时 的 16 
        }
        // 为null值 指定位置
        // table[0]的链表  并返回旧值或null
        if (key == null)
            return putForNullKey(value);
            // 计算hash值
        int hash = hash(key);
        // 为利用hash找到 key应该放到table的某个数组的链下
        int i = indexFor(hash, table.length);
        // 遍历 table[i]链表 找到与当前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;
            }
        }
		// 若table[i]下没有相同的key 则 modCount+1,并执行addEntry将key,value放到map中
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
           

执行addEntry方法将key,value存入map中。

此方法先计算是否需要扩容后真正放值。

//size : The number of key-value mappings contained in this map.
 void addEntry(int hash, K key, V value, int bucketIndex) {
 		// add之前会判断当前map中的数量是否大于等于阈值
 		// 扩容条件 当前size超过阈值 并且 table[bucketIndex] 不为空
        if ((size >= threshold) && (null != table[bucketIndex])) {
        	// 扩容原先的两倍 
            resize(2 * table.length);
            // 重新计算hash值
            hash = (null != key) ? hash(key) : 0;
            // 计算当前key所属的 table数组位置
            bucketIndex = indexFor(hash, table.length);
        }
        createEntry(hash, key, value, bucketIndex);
    }
           

真正的放值方法,

使用的是头插法即将table[bucketIndex]替换为我们的key,并将原先的Entry当作当前Entry的next节点

void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
           

3. resize扩容方法

扩容方法是创建一个新的Entry数组(容量为原先的两倍)

并将原先数组中的值转移到新数组中

// 此处使用了一个全局变量MAXIMUM_CAPACITY 
static final int MAXIMUM_CAPACITY = 1 << 30;//1073741824 即2的30次方
 void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        // 如果当前table容量已经等于最大容量值则将阈值设定为整数最大值并返回
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;//2<sup>31</sup>-1
            return;
        }
		
        Entry[] newTable = new Entry[newCapacity];
        // 转移值 initHashSeedAsNeeded用于返回是否需要重新计算hash
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        // 转移值之后 更改table的引用 
        table = newTable;
        // 更新阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
           

转移数组方法

遍历table 并 循环遍历每个链表 为链表中每个元素计算隶属与数组哪个位置然后转移

注意:HashMap是非线程安全的 转移是会有机会造成循环引用,后面会演示怎样造成循环引用

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

4. remove

public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

    /**
     * Removes and returns the entry associated with the specified key
     * in the HashMap.  Returns null if the HashMap contains no mapping
     * for this key.
     */
    final Entry<K,V> removeEntryForKey(Object key) {
        if (size == 0) {
            return null;
        }
        int hash = (key == null) ? 0 : hash(key);
        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;
            }
            // 修改引用 遍历下一个Entry
            prev = e;
            e = next;
        }

        return e;
    }
           

5. initHashSeedAsNeeded 和 hash

返回是否需要重新计算hash

transient int hashSeed = 0;
final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;// 初始时 false
        
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);// 容量是否超过 默认时Integer最大值
        // 默认为false 但是通过打印可以得知为true 没有查看怎么修改的但从字面意思时虚拟机是否启动
        //private static volatile boolean booted = false;
   		// public static boolean isBooted() {
    	//    return booted;
    	//}

		// 默认 false ^ false 
        boolean switching = currentAltHashing ^ useAltHashing;// 异或预算 不相同时返回true
        if (switching) {// true 时 hashSeed hash 种子
            hashSeed = useAltHashing//false
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }

static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

    /**
     * holds values which can't be initialized until after VM is booted.
     */
    private static class Holder {

        /**
         * Table capacity above which to switch to use alternative hashing.
         */
        static final int ALTERNATIVE_HASHING_THRESHOLD;

        static {
        // 判断是否有此参数
        // 	-Djdk.map.althashing.threshold=-1:表示不做优化(不配置这个值作用一样)
		// -Djdk.map.althashing.threshold<0:报错

		// -Djdk.map.althashing.threshold=1:表示总是启用随机HashSeed
		// -Djdk.map.althashing.threshold>=0:便是hashMap内部的数组长度超过该值了就使用随机HashSeed,降低碰撞
            String altThreshold = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    "jdk.map.althashing.threshold"));
            int threshold;
            try {
                threshold = (null != altThreshold)
                        ? Integer.parseInt(altThreshold)
                        : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;// 默认Integer最大值

                // disable alternative hashing if -1
                if (threshold == -1) {
                    threshold = Integer.MAX_VALUE;
                }

                if (threshold < 0) {
                    throw new IllegalArgumentException("value must be positive integer.");
                }
            } catch(IllegalArgumentException failed) {
                throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
            }
			
            ALTERNATIVE_HASHING_THRESHOLD = threshold;
        }
    }
           
final int hash(Object k) {
        int h = hashSeed;// hashSeed 默认为0
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
		// 异或 
        h ^= k.hashCode();
		
        // 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);
    }
           

6. 多线程 resize 线程不安全演示

转移Entry会造成链表翻转
线程1执行完转移 线程二开始转移
           
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;
            }
        }
           

假定条件:两个线程同时执行到,第一次while next = e.next时, 线程一继续执行,线程二等待线程一执行完后再执行。

e = table[i];

next = 1;

Java 1.7 HashMap源码阅读