• 目录

说说HashMap的原理和扩容

阅读量: 222 编辑

说说HashMap的原理和扩容

HashMap是由数组 + 链表 + 红黑树(JDK1.8加入) 构成的

一、HashMap有一个很重要的属性 Node[] table

Node是一个内部类。

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

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
        
		// 其他代码..
}
  • 这个类的作用是就是存储,本质就是一个映射(键值对)。也就是说一个键值对(key:value)就是一个Node。

  • Node是存储在 Node[] table 中的,存储的方式是 链表结构 或 红黑树

二、HashMap是通过 hash 表来存储的。

  • hash表为了解决冲突,采用的是链地址法,就是数组和链表的组合。

  • 每个数组元素都是一个链表结构。当数据被 hash 后,将数据放到对应下标的链表上。这个数组就是 Node[] table。也被称之为hash桶数组

  • 当key被hashCode()后,就需要确定 键值对 的存储位置。如果定位的是同一个位置,那么就是hash碰撞。如果hash桶数组很大,那么即使Hash算法较差也很少发生碰撞。如果 hash桶数组 很小,即使再好的Hash算法也会发生碰撞。这就需要权衡 空间成功 和 时间成本

三、HashMap中的基本参数

所以为了权衡,HashMap中有几个默认配置

int threshold;  // 键值对的极限值
final float loadFactor;    // 负载因子,默认是 0.75
int modCount;  //扩容的次数

transient Node<K,V>[] table;  //hash桶数组。默认长度 length 必须是 2 的幂次方 是 16
  • length 必须是 2 的幂次方

  • threshold = length * loadFactor

四、HashMap的扩容

  • 如果我们放的数据过多,会导致 Node 中的链表过长,影响性能,所以引入了 红黑树 存储。

  • 当链表的长度大于 8 时,链表就转化为了红黑树,利用红黑树增删查改的特点提高HashMap的性能。

  • 确定元素在hash桶中中位置的hash算法。

static final int hash(Object key) {
        int h;
        // h = key.hashCode()   取hashCode的值
        // h  ^ (h >>> 16)         高位参与运行
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 扩容是针对 Node[] 扩容。参考下方源码。
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;  // resize()扩容
        
    if ((p = tab[i = (n - 1) & hash]) == null)
        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))))
            e = p;
        else if (p instanceof TreeNode)  //判断是否为红黑树
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {  // 链表结构
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 链表长度大于 8 转化为红黑树
                    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;
                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;//扩容次数 + 1
    if (++size > threshold)  // 超过了最大容量,resize() 扩容
        resize();
    afterNodeInsertion(evict);
    return null;
}
  • 目录