HashMap源码探究

来源:互联网 发布:汶川地震知乎万人坑 编辑:程序博客网 时间:2024/06/09 23:22

HashMap的创建

public HashMap() {        table = (HashMapEntry<K, V>[]) EMPTY_TABLE;        threshold = -1; // Forces first put invocation to replace EMPTY_TABLE    }    public HashMap(int capacity) {        if (capacity < 0) {            throw new IllegalArgumentException("Capacity: " + capacity);        }        if (capacity == 0) {            @SuppressWarnings("unchecked")            HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE;            table = tab;            threshold = -1; // Forces first put() to replace EMPTY_TABLE            return;        }        if (capacity < MINIMUM_CAPACITY) {            capacity = MINIMUM_CAPACITY;        } else if (capacity > MAXIMUM_CAPACITY) {            capacity = MAXIMUM_CAPACITY;        } else {            capacity = Collections.roundUpToPowerOfTwo(capacity);        }        makeTable(capacity);    }    public HashMap(int capacity, float loadFactor) {        this(capacity);        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {            throw new IllegalArgumentException("Load factor: " + loadFactor);        }        /*         * Note that this implementation ignores loadFactor; it always uses         * a load factor of 3/4. This simplifies the code and generally         * improves performance.         */    }

构造函数中涉及到了capacity、loadFactor两个参数,这两个参数是影响HashMap性能的重要参数。

capacity代表了创建哈希表的初始容量,loadFactor代表哈希表容量自动增加之前可以达到多满,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75

==事实上虽然第三个构造函数中可以指定loadFactor但是实际HashMap实现中并没有使用参数中的值而只是使用了默认的0.75==

image

如图:HashMap底层实现为数组table,数组的每一项又是一个链表HashMapEntry,而构造函数中capacity指的就是数组的长度

    /**     * Allocate a table of the given capacity and set the threshold accordingly.     * @param newCapacity must be a power of two     */    private HashMapEntry<K, V>[] makeTable(int newCapacity) {        @SuppressWarnings("unchecked") HashMapEntry<K, V>[] newTable                = (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity];        table = newTable;        threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity        return newTable;    }    static class HashMapEntry<K, V> implements Entry<K, V> {        final K key;        V value;        final int hash;        HashMapEntry<K, V> next;        HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {            this.key = key;            this.value = value;            this.hash = hash;            this.next = next;        }        ...    }

HashMap的数据存入

    @Override public V put(K key, V value) {        //key为空时 调用putValueForNullKey方法单独处理,这也是为什么HashMap支持null数据存取的原因        if (key == null) {            return putValueForNullKey(value);        }        //计算key的hash值        int hash = Collections.secondaryHash(key);        HashMapEntry<K, V>[] tab = table;        //!!!计算hash对应的数组位置(h&(length - 1)就相当于对length取模,而且速度比直接取模快得多,这是HashMap在速度上的一个优化。)        int index = hash & (tab.length - 1);        //tab是否存在该hash值,如果存在则在数组对应的链表中查找是否有对应的key,如果有则新值覆盖旧值        for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {            //存在hash值和key值均相同的数据,执行新值覆盖旧值的动作            if (e.hash == hash && key.equals(e.key)) {                preModify(e);                V oldValue = e.value;                e.value = value;//新值覆盖旧值                return oldValue;//返回旧值            }        }        //上面没有找到对应的key值 创建一个        // No entry for (non-null) key is present; create one        modCount++;        //判断是否需要扩容        if (size++ > threshold) {            tab = doubleCapacity();            index = hash & (tab.length - 1);        }        //数组添加新的节点        addNewEntry(key, value, hash, index);        return null;    }

HashMap的读取

    public V get(Object key) {        //key为null时,调用entryForNullKey得到对应的value        if (key == null) {            HashMapEntry<K, V> e = entryForNullKey;            return e == null ? null : e.value;        }        //计算key对应的hash值        int hash = Collections.secondaryHash(key);        HashMapEntry<K, V>[] tab = table;        //查找tab是否存在对应的key,如果存在返回对应的value        for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];                e != null; e = e.next) {            K eKey = e.key;            if (eKey == key || (e.hash == hash && key.equals(eKey))) {                return e.value;//返回查找的value            }        }        return null;    }

HashMap的底层数组长度总是2的n次方,在构造函数中存在:capacity <<= 1;这样做总是能够保证HashMap的底层数组长度为2的n次方。当length为2的n次方时,h&(length - 1)就相当于对length取模,而且速度比直接取模快得多,这是HashMap在速度上的一个优化

HashMap中需要做到尽量的分布均匀且充分利用空间,这块的算法后面单说。

0 0
原创粉丝点击