【源码探索系列】jdk1.7HashMap源码分析

327 阅读5分钟

initialCapacity

容量,默认是16,用来决定Entry数组的长度,Entry数组的长度是一个大于或等于initialCapacity的2的n次方数

loadFactor

加载因子,默认0.75,和Entry数组的长度一起来决定阈值threshold

threshold

阈值,threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1),阈值是用来决定是否扩容的条件

hashSeed

hashSeed来计算并判断的,hashSeed默认为零,判断是否需要给haseSeed赋值是通过判断容量是否大于某个配置的环境变量值(如果没有配置这个值,默认为Integer.MAX_VALUE), 所以默认情况下hashSeed都为0,rehash都为false。hashSeed的作用是通过影响hash算法来让元素分布的更散列

初始化

初始化操作主要是用来对loadFactor和threshold进行赋值操作

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

put方法

put方法主要做的事:

  1. 如果table数组是空,就计算数组容量,threshold和hashSeed,并创建一个新的Entry数组
  2. 如果key是null,则去table[0]去遍历链表,如果有key为null的就替换,否则创建一个新节点用头插法插入到链表中
  3. 通过hash值计算key在table中的下标,遍历链表如果找到了key相等的就返回
  4. 如果链表中没有相等的则区创建一个并用头插法插入到链表中,创建之前先去判断是否需要扩容,如果需要扩容则先扩容并把老数组中的元素转移到新数组中
public V put(K key, V value) {
    	// 如果Entry数组是空数组就创建
        if (table == EMPTY_TABLE) {
            // 计算数组容量,创建Entry数组,计算threshold和hashSeed
            inflateTable(threshold);
        }
        if (key == null)
            // 如果key是null,则去table[0]去遍历链表,如果有key为null的就替换
            // 否则创建一个新节点用头插法插入到链表中
            return putForNullKey(value);
    	// 先用hashSeed和key.hashCode进行抑或得到h
    	// 然后再对h进行多次右移和抑或操作
    	// 右移操作是为了让高位也参与运算
    	// 多次右移和抑或操作是为了让hash值更加的分散
        int hash = hash(key);
    	// 通过hash值计算key在table中的下标
    	// 计算方式为 hash & (table.length - 1)
        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;
            }
        }
		
    	// 修改次数加1
        modCount++;
    	// 没有相等的则创建一个并头插法插入到链表中
        addEntry(hash, key, value, i);
        return null;
    }

inflateTable

private void inflateTable(int toSize) {
    // 找到一个大于或等于toSize的2的n次方数
    int capacity = roundUpToPowerOf2(toSize);

    // 计算threshold
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    // 初始化hashSeed
    initHashSeedAsNeeded(capacity);
}

roundUpToPowerOf2

private static int roundUpToPowerOf2(int number) {
    // 如果number大于或等于MAXIMUM_CAPACITY则number = MAXIMUM_CAPACITY
    // 如果number小于1则number = 1
    // 如果number大于1,则先将(number - 1)左移一位即扩大两倍
    // 然后再调Integer.highestOneBit方法找到比它小的最大的2的n次方数(这个操作有点sao)
    return number >= MAXIMUM_CAPACITY
        ? MAXIMUM_CAPACITY
        : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}

initHashSeedAsNeeded

final boolean initHashSeedAsNeeded(int capacity) {
    	// 默认为false
        boolean currentAltHashing = hashSeed != 0;
        // jvm是否启动且容量是否大于Holder.ALTERNATIVE_HASHING_THRESHOLD
    	// Holder.ALTERNATIVE_HASHING_THRESHOLD于"jdk.map.althashing.threshold"这个配置有关
        // 如果配置了就取这个值,如果没配置就取Integer.MAX_VALUE
    	// 此处默认为false
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
    	// 此处默认为false,所以hashSeed默认为0
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }

addEntry

void addEntry(int hash, K key, V value, int bucketIndex) {
    	// 如果size大于等于阈值且当前链表的头节点不是空
    	// 那也就是说阈值并不是唯一确定是否扩容的条件
        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);
}

扩容

扩容的是为了让链表的长度变短,提升查询效率,transfer方法会出现循环链表问题,导致put的时候出现死循环,因为头插法在扩容时会出现链表逆序的问题,在多线程扩容的情况下会造成循环链表问题,

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

transfer方法

  1. transfer方法主要是用来将老数组的元素转移到新数组,其中有rehash标识依赖于initHashSeedAsNeeded方法的返回值,上面已经分析过该方法的返回值默认为false,所有默认情况下是不需要rehash的,而且在jdk1.8中直接把rehash的判断去掉了
  2. 将老数组的元素转移到新数组后元素顺序变成了逆序
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);
                }
                // 用原来的hash值计算出来的结果等同于 newIdx = oldeIdx + oldTable.length
                int i = indexFor(e.hash, newCapacity);
                // 插入到新数组的顺序变成了逆序
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

扩容引发的循环链表问题

出现循环链表的情况:

modCount

修改次数,modCount的作用是fast-fail容错机制

public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

private final class KeySet extends AbstractSet<K> {
    public Iterator<K> iterator() {
        // 返回的是KeyIterator对象
        return newKeyIterator();
    }
    public int size() {
        return size;
    }
    public boolean contains(Object o) {
        return containsKey(o);
    }
    
    
    public boolean remove(Object o) {
        return HashMap.this.removeEntryForKey(o) != null;
    }
    public void clear() {
        HashMap.this.clear();
    }
}

// hashMap.keySet()返回的是KeySet对象,KeySet对象内部的iterator会返回一个KeyIterator对象
// KeyIterator继承的是HashIterator,当调用KeyIterator的next方法时会调用HashIterator的nextEntry方法
// 此时会判断modCount和expectedModCount是否相等,不相等就会报错
final Entry<K,V> nextEntry() {
    // 此时会判断这两个值是否相等
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    Entry<K,V> e = next;
    if (e == null)
        throw new NoSuchElementException();

    if ((next = e.next) == null) {
        Entry[] t = table;
        while (index < t.length && (next = t[index++]) == null)
            ;
    }
    current = e;
    return e;
}

for(String key : hashMap.keySet()) {
    // 调用的hashMap.remove会修改modCount的值,但是不会修改expectedModCount的值
    hashMap.remove(key); // 此时会抛异常
    // 解决办法是使用iterator的remove方法,此时会调用HashIterator的remove方法
    // remove方法内部会调用hashMap的remove方法并让expectedModCount = modCount
}