HashMap 底层实现原理

1、HashMap的底层数据结构

HashMap 的底层实现原理,1.71.8 是有区别的
1.7 及之前的版本,底层是 数组 + 链表 Entry
1.8 及之后的版本,底层是 数组 + 链表 + 红黑树 NOde,引入红黑树主要是因为当 Hash 冲突较多时,链表就会变成长链表,操作效率会变慢。
// TREEIFY_THRESHOLD = 8 树化阈值 
// UNTREEIFY_THRESHOLD = 8 取消树化阈值
引入红黑树的原则是,当链表长度大于 8 时,链表会变成红黑树;当红黑树节点小于 6 时,红黑树又会变成链表。
底层就是一个数组,数组里面的元素是 Node<K,V> 链表
transient Node<K,V>[] 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;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
}

2、Java7和Java8的区别

1、数据结构不同
    1.71.7 之前的版本是:数组 + 链表
    1.81.8 之后的版本是:数组 + 链表 + 红黑树(为了解决Hash冲突,当Hash冲突较多时,链表长度过长,
    会影响数据操作效率,降低时间复杂度,由O(n) -> O(logn)2、存储数据时插入方法不一样
    1.71.7 之前采用的是:头插法
    1.81.8 之后采用的是:尾插法
    头插法,在多线程操作下,扩容的时候可能会产生环形链表,取值的时候,就会出现 Infinite Loop。
    尾插法就不会出现这样的问题,永远放在链表的尾部,扩容的时候,不会改变链表的顺序,不会出现环形链表。  
总结:
Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。
Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。     

3、HashMap的主要参数都有哪些

// initialCapacity 初始容量 不传的话 默认为16  1 << 4
// loadFactor 负载因子 不传的话 默认为0.75
public HashMap(int initialCapacity, float loadFactor);
// 构造方法有3个
// 无参构造方法 初始容量 为 16 负载因子为 0.75
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 一个参数构造方法 初始容量为传入容量 会转成 大于等于传参的最小的2的幂,负载因子为 0.75
public HashMap(int initialCapacity) {
    this(initialCapacity, 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;
    this.threshold = tableSizeFor(initialCapacity);
}

4、默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?

// 默认初始化大小为 16 必须为2的幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量为 1 << 30  10 7374 1824,如果初始化时传参大于最大容量,则使用该容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 为啥是 2 的幂, tableSizeFor方法,对传入的容量做了以下操作
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1; // n 和 n >>> 1 做 或 操作
    n |= n >>> 2; // n 和 n >>> 2 做 或 操作
    n |= n >>> 4; // n 和 n >>> 4 做 或 操作
    n |= n >>> 8; // n 和 n >>> 8 做 或 操作
    n |= n >>> 16; // n 和 n >>> 16 做 或 操作
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
// 这样一番操作下来,结果只能是 2 4 8 16 32 ... 2^n,都是 2 的幂
// 使用 2 的幂的好处 --> 尽可能实现均匀分布
// 因为 HashMap 的下标算法为:index = HashCode(Key) & (Length- 1)
// 使用 2 的幂的时候,Length- 1 的二进制就都是1,和 HashCode(Key)与操作的时候,index的结果就等于 HashCode(Key)的后面几位
// 只要输入的 HashCode(Key)本身分布均匀,算出来的 index 就是均匀的,可以实现均匀分布。

5、hash的计算规则

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
计算规则:key.hashCode() ^ (key.hashCode() >>> 16);

6、HashMap的存取原理

// HashMap 存储数据原理 --> put
// 参数 hash: key 的hash值 key: key value: 存入的value 
// onlyIfAbsent: 是否覆盖当前 key 对应的已存在的value值,如果为true时,表示不覆盖,false 表示覆盖,默认为 false
// evict: 
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 如果 table 为空,没有初始化
    if ((tab = table) == null || (n = tab.length) == 0)
    // 调用resize() 方法,初始化table,长度为默认长度 16,负载因子为默认负载因子 0.75
        n = (tab = resize()).length;
    // tab[i = (n - 1) & hash] 是通过hash求出下标,如果当前下标的元素为 null,直接生成一个链表放在下标所在的位置
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 如果下标所在的链表,头部已经有元素,执行下面的操作 
    else {
        Node<K,V> e; K k;
        // 如果当前key 已存在,把p赋给e,临时存储
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 如果 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);
                    // 如果链表长度已达到变成红黑树的阈值,要把链表变成红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 如果当前key已存在,直接返回
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                // 元素递归操作    
                p = e;
            }
        }
        // 如果当前 key 已存在,判断是否要元素覆盖
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    // HashMap 结构调整的次数
    ++modCount;
    // 如果长度大于阈值,需要扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
// resize() 方法
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 {               // zero initial threshold signifies using defaults
        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)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    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;
}
// HashMap 的 读取原理
// 读取通过 key 
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    // 判断 数组不为空,数组长度不为空,下标所指向的链表头部节点数据不为空
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        // 判断是否和头部元素相等,相等就返回    
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        // 链表遍历    
        if ((e = first.next) != null) {
            // 如果当前元素属于红黑树,走红黑树的获取元素方法
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            // 不是红黑树,遍历链表,获取元素    
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页