HashMap源码解析

x33g5p2x  于2022-07-06 转载在 Java  
字(11.3k)|赞(0)|评价(0)|浏览(379)

HashMap概述

Map 接口的基于哈希表的实现。此实现提供所有可选的映射操作,并允许空值和空键。(HashMap 类大致相当于 Hashtable,除了它是不同步的并且允许空值。)这个类不保证映射的顺序;特别是,它不保证顺序会随着时间的推移保持不变。
此实现为基本操作(get 和 put)提供恒定时间性能,假设哈希函数将元素正确地分散在桶中。集合视图的迭代需要的时间与 HashMap 实例的“容量”(桶的数量)加上它的大小(键值映射的数量)成正比。因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载因子太低),这一点非常重要。

HashMap 的实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的桶数,初始容量只是哈希表创建时的容量。负载因子是哈希表在其容量自动增加之前允许达到的程度的度量。当哈希表中的条目数超过负载因子和当前容量的乘积时,对哈希表进行重新哈希(即重建内部数据结构),使哈希表具有大约两倍的桶数。(每次扩容容量是原来的2倍)

作为一般规则,默认负载因子 (0.75) 在时间和空间成本之间提供了良好的折衷。较高的值会减少空间开销,但会增加查找成本(反映在 HashMap 类的大多数操作中,包括 get 和 put)。在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以尽量减少重新哈希操作的次数。如果初始容量大于最大条目数除以负载因子,则不会发生重新哈希操作。

如果要在 HashMap 实例中存储许多映射,则创建具有足够大容量的映射将比让它根据需要执行自动重新散列以增长表来更有效地存储映射。(尽量在一开始指定合适的容量减少扩容)

请注意,此实现不同步。(非线程安全的)如果多个线程同时访问一个哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。 **(结构修改是添加删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改。)**这通常通过在自然封装映射的某个对象上同步来完成.如果不存在这样的对象,则应使用 Collections.synchronizedMap 方法“包装”map。这最好在创建时完成,以防止对map的意外不同步访问:Map m = Collections.synchronizedMap(new HashMap(...));
所有此类的“集合视图方法”返回的迭代器都是快速失败的:如果在创建迭代器后的任何时间对映射进行结构修改,除了通过迭代器自己的 remove 方法之外,迭代器将抛出 ConcurrentModificationException .因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒任意的、非确定性的行为。请注意,不能保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。
快速失败的迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写一个依赖于这个异常的正确性的程序是错误的:迭代器的快速失败行为应该只用于检测错误。

JDK 1.7

底层:数组+链表

默认参数:

// 默认容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 定义一个空数组
static final Entry<?,?>[] EMPTY_TABLE = {};
// 存储键值对的数组,默认为空数组,根据需要进行扩容,长度必须是2的整数幂
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
// 容量*加载因子,根据此判断是否需要扩容
int threshold;
// map中的键值对个数
transient int size;
// 此 HashMap 已在结构上修改的次数。结构修改是指更改 HashMap 中的映射数量或以其他方式修改其内部结构(例如,重新散列)的那些。该字段用于使 HashMap 的 Collection-views 上的迭代器快速失败。 (请参阅 ConcurrentModificationException)。
// 结构上的修改一般来说就是添加和删除
transient int modCount;

构造方法:

初始化:容量,加载因子,entry数组

public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    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操作:

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            // 如果是空数组,根据容量进行初始化,
            inflateTable(threshold);
        }
        if (key == null)
            // 有则更新,无则新增,下标为0
            return putForNullKey(value);
        // 根据key取hash值
        int hash = hash(key);
        // 根据hash值求取下标
        int i = indexFor(hash, table.length);
        // 如果存在旧值,就更新并返回旧值
        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;
            }
        }

        modCount++;
        // 新增一个
        addEntry(hash, key, value, i);
        return null;
    }

     private void inflateTable(int toSize) {
        // 容量是2的整数幂
        int capacity = roundUpToPowerOf2(toSize);

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

    final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            // 最后结果是 hashSeed=0
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }

     private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
    // 原理(扰动函数),尽可能使生成的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);
    }

    // 当length始终为2的n次方时,h&(length-1)等价于h%length
    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);
    }

    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length); // 当size大于容量*负载因子的时候进行扩容
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
    // 新增一个entry
    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++;
    }

    // 扩容
    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);
    }
    // 将旧数组的元素转移到新数组
    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);
                // 这里采用了“头插法”,相当于倒序插入
                // 假如原来的链表为 a->b->c->d->null
                // 转移后的新的链表为 d->c->b->a->null
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

JDK 1.8

底层:数组+链表+红黑树
当链表元素个数大于8的时候,并且数组长度大于64时,进行树化
红黑树结构在进行扩容时,如果节点元素个数小于6时,转为链表

默认参数

// 数组由1.7的Entry[K,V]->Node<K,V>
transient Node<K,V>[] table;
// 树化时bin的计数阈值
static final int TREEIFY_THRESHOLD = 8;
// 树化转为链表bin的计数阈值
static final int UNTREEIFY_THRESHOLD = 6;
// 进行树化的bin的最小表容量
// 当bin的数量达到计数阈值可以进行树化时,如果表容量小于此值,则先进行扩容
static final int MIN_TREEIFY_CAPACITY = 64;

构造方法

初始化:容量、加载因子、Node数组

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;
        this.threshold = tableSizeFor(initialCapacity);
    }

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; 
    }
    // 保证容量是2的整数幂
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

put操作:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    // 1.8对hash算法进行了优化
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length; // 未初始化时,先调用扩容方法进行初始化
        if ((p = tab[i = (n - 1) & hash]) == null) // (n - 1) & hash 就是计算key的下标位置
            tab[i] = newNode(hash, key, value, null); // 新增
        else { // 说明当前位置已经有元素了
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) // 相同的key值则替换旧值
                e = p;
            else if (p instanceof TreeNode) // 树化的节点添加
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {  // 非树化节点添加时,遍历bin
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) { //遍历到最后一个元素了
                        p.next = newNode(hash, key, value, null); // 新添加
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash); // 进行树化
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break; // 找到相同的key,结束遍历
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value; // 更新旧值
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();  // 扩容
        afterNodeInsertion(evict);
        return null;
    }
    // 创建一个非树节点
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }

扩容和树化

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // 初始化,默认容量16,负载因子0.75
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) { // 从数组的第一个元素开始遍历
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null) // bin只有一个元素
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode) // 树化了的bin,如果链表元素小于6时,转为链表
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // 维护秩序,扩容前后的顺序一致
                        Node<K,V> loHead = null, loTail = null; // 低位的链表,记录hash值&数组长度,等于0 的,说明下标小于数组长度
                        Node<K,V> hiHead = null, hiTail = null; // 高位的链表,记录说明hash值&数组长度=数组长度,下标大于等于数组长度
                        Node<K,V> next;
                        // 这里的插入方法也被称为“尾插法”
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) { 
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {  
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) { // 低位的链表元素下标不变
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) { // 高位的链表元素下标是要加上旧数组的长度
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
    // 树化
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) // 表容量小于64,则不树化,而是进行扩容
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null; // hd:根节点 tl:临时尾结点

            do {
                TreeNode<K,V> p = replacementTreeNode(e, null); // 转为TreeNode
                if (tl == null)
                    hd = p; // 说明刚开始,设置根节点
                else {
                    p.prev = tl; // 设置当前节点的上一个节点为尾结点
                    tl.next = p; // 尾结点的下一个节点为当前节点
                }
                tl = p; // 设置尾结点为当前节点
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
    // 将普通Node替换为TreeNode
    TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
        return new TreeNode<>(p.hash, p.key, p.value, next);
    }

        final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
            TreeNode<K,V> b = this;
            // Relink into lo and hi lists, preserving order
            TreeNode<K,V> loHead = null, loTail = null;
            TreeNode<K,V> hiHead = null, hiTail = null;
            int lc = 0, hc = 0;
            for (TreeNode<K,V> e = b, next; e != null; e = next) {
                next = (TreeNode<K,V>)e.next;
                e.next = null;
                if ((e.hash & bit) == 0) {
                    if ((e.prev = loTail) == null)
                        loHead = e;
                    else
                        loTail.next = e;
                    loTail = e;
                    ++lc;
                }
                else {
                    if ((e.prev = hiTail) == null)
                        hiHead = e;
                    else
                        hiTail.next = e;
                    hiTail = e;
                    ++hc;
                }
            }
            
            if (loHead != null) { 
                if (lc <= UNTREEIFY_THRESHOLD) // 个数小于6时转为链表
                    tab[index] = loHead.untreeify(map);
                else {
                    tab[index] = loHead;
                    if (hiHead != null) // (else is already treeified)
                        loHead.treeify(tab);
                }
            }
            if (hiHead != null) {
                if (hc <= UNTREEIFY_THRESHOLD) // 个数小于6时转为链表
                    tab[index + bit] = hiHead.untreeify(map);
                else {
                    tab[index + bit] = hiHead;
                    if (loHead != null)
                        hiHead.treeify(tab);
                }
            }
        }

HashMap put操作流程总结

JDK 1.7 put流程

JDK 1.8 put流程

相关文章