HashMap源码解析

    xiaoxiao2022-07-14  153

    Hash Map源码

    HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一。

    属性的定义

    public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { //设置容器的默认初始化大小 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //设置容器的最大上限 static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //桶中元素大于8的时候,从链表转成红黑树 static final int TREEIFY_THRESHOLD = 8; //桶中元素小于6的时候,从红黑树专程链表 static final int UNTREEIFY_THRESHOLD = 6; //桶中结构转化为红黑树对应的table的最小大小 static final int MIN_TREEIFY_CAPACITY = 64; //哈希数组,存放元素,总是2的次方 transient Node<K,V>[] table; //存放具体元素的集 transient Set<Map.Entry<K,V>> entrySet; //存放原始的个数 transient int size; //map改变的次数,迭代的时候,快速失败用 transient int modCount; //临界值,大于时进行扩容,容量*填充因子;保存第一次设置的容器默认初始化大小 int threshold; //加载因子 final float loadFactor;

    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; } //判断相等,如果key和value都相等就返回true public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }

    构造方法

    1)无参构造方法

    public HashMap() { //把加载因子设置成默认的加载因子,其他都默认 this.loadFactor = DEFAULT_LOAD_FACTOR; }

    2) 有初始化大小的构造构造方法

    最开始只是把设置的默认初始化大小的值设置在了threshold中,在第一次添加元素的时候才真正的初始化容器数据的大小。

    //设置初始化大小 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); }

    tableSizeFor

    保证是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; }

    3)参数为map的构造函数

    public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }

    putMapEntries

    实现一个map复制进入本Map中,在构造函数和Map.putAll的时候调用

    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { //如果table为空,设置threshold的值,还是在第一次put时,再扩容 if (table == null) { float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } //如果不为空,且map的大小超过了临界值,直接扩容 else if (s > threshold) resize(); //遍历数组,扩容。 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } }

    key的哈希值计算

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

    调用key自身的hashCode方法,然后自身的高16位异或低16位

    例: 11111111 01001000 11001100 11001100 //原始的二进制

    ​ 00000000 00000000 11111111 01001000 //向后移位16位

    异或结果: 11111111 01001000 00110011 10000100

    put方法

    添加元素进入map,实际上是调用putVal完成任务。

    public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }

    putVal

    真正的添加操作

    hash key的哈希值keyvalueonlyIfAbsent 如果为真,则不更改现有值evict 如果是false,表示这是构造方法调用的,true表示是put时调用的

    添加顺序

    首先判断table数组是否为空,如果为空就进行扩容通过key的哈希值计算下标,如果数组相应位置为空,直接添加如果不为空, 首先判断原始值的key和当前key是否相等,如果替换当前位置判断当前节点是不是一个树节点,如果是红黑树就在红黑树中插入当前是链表节点,遍历整个链表,在尾部添加,或者替换相应位置的节点如果节点数大于的8,就把链表转化为红黑树 判断是否需要扩容

    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; //如果数组相应位置为空,直接添加 (n-1)&hash获取元素在数组中的下标 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else {//不为空的情况 Node<K,V> e; K k; //p为原始的值,如果原始值的key和当前相等,替换当前位置 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; } } //如果e不为空,即在链表或红黑树中找到了相应的值就替换 if (e != null) { // existing mapping for key V oldValue = e.value; //如果允许替换 if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } //改变次数+1 ++modCount; //判断是否扩展 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }

    resize扩展方法

    当map中的元素个数大于threshold临界值(容量*加载因子)时,或者第一次添加元素的时候,就需要进行扩容

    jdk1.8的扩容和jdk1.7的扩容有很大的区别

    因为jdk1.8的大小都是2的幂倍数,每次都是扩容2倍,就是把上一次的哈希值向前扩充一个2进制位,那么新的哈希值要么还在原来的位置,要么它的哈希值=原来的哈希值+原来的容器大小。

    因此一个桶里面的节点在扩容后要么还在原来的位置,要么再加上原来的容器大小,只有这两种可能,所以扩容时,只需要遍历每个桶的所有节点,并分成两类就可以了。

    不懂就学习参考链接1的地址,很详细。

    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; } //设置新的容器大小和新的临界值newThr 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); } //把局部临界值赋值给map实例的全局临界值。 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; //如果当前是一颗红黑树, 大于8还是要转化为红黑树 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { //当前是链表 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; }

    get方法

    获取元素

    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; }

    参考链接

    美团:Java 8系列之重新认识HashMapJavaGuide中HashMap源码解析
    最新回复(0)