HashMap源码分析

hashmap内中维护的是Entry类型的数组以及单向链表。Entry类型是静态内部类,内含key,value,next,hash四个属性。
hashmap中还有两个重要的参数:threshold和loadFactor,分别为扩容阈值和负载因子,它们之间的关系为threshold=capacity*loadFactor。

构造器

hashmap有多个重载的构造器,最核心的构造器为

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

其余构造器都是调用此构造器。另外有一个由已有map对象构造的构造器如下:

public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);

        putAllForCreate(m);
    }

    private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);

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

此构造器先调用之前构造器进行构造,此时阈值只是最小默认容量和入参对象的size/默认负载因子的最大值,然后调用inflateTable方法计算大于等于该阈值的最小的2的幂函数,令该幂函数的值为容量大小。最后给给数组赋值。

基础设施

indexFor()/hash()方法

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);   //等价于h%length
    }

indexFor()方法用于返回hash值对应的数组索引。属于访问table数组的前哨方法,访问table数组都需要先让该方法通过hash和数组长度计算索引再执行访问。hash值就像是对象的ID用来标识对象。

因为前面数组的长度始终是2的幂函数,则length-1生成低位掩码(如:length=16,length-1=15,二进制形式即为00000000 00000000 00000000 00001111),与h按位与返回的是h模length。但是对于int这么大的值空间,每次都取低位相与,比较容易碰撞,所以hashmap还有一个函数hash()对key的hash值进行预处理:

final int hash(Object k) {
        int h = hashSeed;
        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);
    }

这段代码叫扰动函数。顾名思义,就是“扰动”参数对象的hash值,重点是要使其低位部分更具有随机性,以减少调用indexFor时碰撞的可能性。这里各种各种右移和异或操作,即为扰动过程,高位和低位异或也变相保留了原hash值的高位特征。

indexFor和hash都是访问数组的前哨方法,先hash扰动,再计算索引。

hashmap中key为null的Entry对象始终存储在table[0]位置上,即null始终在数组的第一个位置上,null对应的hash值为0。当然其他不是null的key,也可能将Entry对象存储在table[0]的链表中。

Entry< K,V>对象

静态内部类Entry类,实现了Map接口的内部接口Entry,是Hashmap的逻辑上的数据单元。其中next属性,用于形成单向链表

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        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;
        }

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

createEntry(int hash,K key,V value,int bucketIndex)

包访问权限。
创建一个Entry,现将原桶位上的Entry的引用保存到局部变量,然后在该桶位上创建新Entry对象,最后将原Entry对象的引用赋值给新对象的next属性。
这样会保证桶位链表最新存入的数据会在第一个位置上,由于概率上新写入的数据很可能在将来不久会继续访问,放在第一个位置上减少遍历链表的开销。

transfer(Entry[] newTable,boolean rehash)

将旧table中的Entry对象都转移至新数组。遍历旧table对象,对于每一个Entry对象,调用indexFor方法在新数组中寻找桶位,将该桶位原有值引用赋值给next属性,将新Entry对象引用赋值给桶位。

resize(int newCapacity)

新建数组,调用transfer方法,然后将新数组引用赋值给当前map。

addEntry(int hash,K key,V value,int bucketIndex)

不同于createEntry(),addEntry需要考虑resize问题,createEntry()单纯只负责创建。
先检查size是否超过阈值,同时bucketIndex不为null,则将原数组扩大两倍,然后在新数组上createEntry。

putForNullKey(V value)

1、直接取table[0]上的Entry对象,遍历该链表,如果存在key为null的Entry对象,则用新值覆盖旧值;如果不存在,则调用addEntry方法添加。2、如果该链表没有key为null的Entry,则调用addEntry在table[0]的桶位新增Entry对象。

put(K key,V value)

在map中新增Entry。1、如果key为null,调用putForNullKey()。
2、执行前哨方法(先扰动hash,然后由新hash和tablelength经indexFor获取index),找到桶位,如果存在链表,则遍历查找看是否有同样的key的Entry,修改其value为新值;如果不存在链表或者没有同样的key,则调用addEntry()。

putForCreate(K key,V value)

对每个Entry,先执行前哨方法,然后再找table中已有的进行覆盖,然后调用createEntry()。

putAllForCreate(Map< ? extends K,? extends V> m)

构造器中从一个map对象构造hashmap,对map中的每个Entry,调用putForCreate()。

putAll(Map< ? extends K,? extends V> m)

先保证容量,然后对每个Entry,调用put()方法

put方法分两类,一类是在已有map上增加(add),一类是构造(create)。两者的区别在于,add时需要考虑容量的问题,修改modCount值。而create不需要考虑这些问题。

removeEntryForKey(Object key)

先执行前哨方法找到桶位,然后遍历链表,修改modCount和size,修改桶位引用和链表引用。

remove(Object key)

调用removeEntryForKey(),与它不同的是它返回Entry,而remove只返回value。

removeMapping(Object o)

删除一个Entry,并返回该Entry。如果o不是Map.Entry的实例,返回null。

clear()

调用Arrays.fill()方法,填充数组各个桶位的引用为null。modCount++,size =0。

直接调用put()方法。

getForNullKey()

遍历table[0]。

getEntry(Object key)

执行前哨方法,找到桶位,遍历链表。

get()

null判断,再getEntry()

containsKey()

判断getEntry的结果

containsValue(Object value)

两级for循环遍历数组和链表

迭代器

通过一个map进行迭代要比collection复杂,因为map不提供迭代器,而是提供三种方法,将map对象的视图作为collection对象返回。由于这些视图本身就是collection,因此它们可以被迭代。

内部抽象类HashIterator实现了Iterator接口

private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;        // next entry to return
        int expectedModCount;   // For fast-fail
        int index;              // current slot
        Entry<K,V> current;     // current entry

        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                //迭代语句
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        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;
                //继续寻找下一个引用不为null的桶位,这里利用while循环不断的判断条件,并在判断的过程之中给next赋值。直到next不为null,继续遍历链表
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
            current = e;
            return e;
        }

        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }
    }

构造器中将table[0]中的引用赋值给next,此时next拿到数组第一个Entry对象,然后访问该对象的next属性,遍历链表,如果链表遍历完成,则寻找下一个不为null的桶位,继续遍历链表…..直到数组中所有Entry都遍历完成。

HashIterator有三个子类,分别实现了各自的next()方法。

private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }

    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }

    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
    }

视图

HashMap有三个视图:keySet(),values(),entrySet(),这三个视图均由HashIterator返回的Entry对象直接支持,而HashIterator由HashMap本身直接支持,所以对视图的操作将直接影响到HashMap本身,相反也成立。
keySet()和entrySet()均继承了AbstractSet类,values()则继承了AbstractCollection类。这些类的迭代器实现由上面三个迭代器分别提供支持。其他方法,由HashMap本身属性和方法提供支持。

//keySet视图
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() {
            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();
        }
    }
//values视图
public Collection<V> values() {
        Collection<V> vs = values;
        return (vs != null ? vs : (values = new Values()));
    }

    private final class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return newValueIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
//entry视图
public Set<Map.Entry<K,V>> entrySet() {
        return entrySet0();
    }

    private Set<Map.Entry<K,V>> entrySet0() {
        Set<Map.Entry<K,V>> es = entrySet;
        return es != null ? es : (entrySet = new EntrySet());
    }

    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

参考内容

https://www.zhihu.com/question/20733617/answer/111577937

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值