HashMap源码分析

来源:互联网 发布:外国人审美 知乎 编辑:程序博客网 时间:2024/05/17 03:56

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

原创粉丝点击