HashMap JDK11

HashMap

HashMap继承自AbstractMap,实现了Map:

HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>

流程:

  1. 计算hashcode

  2. 高位无符号右移16位以参与异或运算(大多数length一般都小于2^16即小于65536)

  3. 取模:(length-1)& hash(就是低位全1,保留了hashcode的零散性质)

  4. 扩容:数组的长度或扩大到原来的2,4,8倍以上,这样做的原因是,取余的时候很好算

    原hash新增位是1,新索引位置就是加一个旧的容量值,新增位是0,则不变

1.1 几个重要的成员变量:

transient int size;				//
transient int modCount;			        // HashMap被改变的次数
int threshold;					// 门限值
final float loadFactor;			        // 装载因子

static class Node<K, V> implements Entry<K, V>{}// 静态内部类,实现链表

transient HashMap.Node<K, V>[] table;		// 实现数组-链表
transient Set<Entry<K, V>> entrySet;		//

initialCapacity默认为16,loadFactory默认为0.75,容量等于16*0.75=12

1.2 构造函数:

HashMap提供了四个构造函数,用于指定门限值和装载因子:

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0) {
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    } else {
        if (initialCapacity > 1073741824) {
            initialCapacity = 1073741824;
        }

        if (loadFactor > 0.0F && !Float.isNaN(loadFactor)) {
            this.loadFactor = loadFactor;
            this.threshold = tableSizeFor(initialCapacity);
        } else {
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
        }
    }
}

1.3 Put方法

索引冲突的几种种情况:

  1. key值相同:进行值覆盖

  2. key值不同,hash相同:链表或者树

  3. key值不同,hash不同,取模后相同(索引位置相同):链表或者树

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

// hash 计算hash值
static final int hash(Object key) {
    int h;
    // 支持 key = null 的形式,
    // hashcode ^ (h >>> 16)
    // 高位参与运算,大多数length一般都小于2^16即小于65536
    return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
}

// putval
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){
    HashMap.Node[] tab; // Node类实现了一个链表数组
    					// 数组的长度总是 2的n次方
    
    int n;				// n是tab的长度,第一次put或者中途扩容的时候会重新设置
    if ((tab = this.table) == null || (n = tab.length) == 0) {
        n = (tab = this.resize()).length;
    }
    
    Object p;
    int i;
    // 根据hash计算索引位置,若空则插入一个node
    if ((p = tab[i = (n - 1) & hash]) == null) {
        tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
    } else { //说明待插入位置存在元素
        Object e;
        Object k;
        
        if(((HashMap.Node)p).hash == hash && 
           ((k = ((HashMap.Node)p).key) == key || 
             key != null && key.equals(k))){ 
            
            e = p;
        } else if(p instanceof HashMap.TreeNode){ // p是红黑树
            e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
        } else {
            int binCount = 0;
            
             while(true) { 
                 // 遍历这个节点,如果 binCount > 7 转为红黑树,
                 // code
                 break;
                 // 否则接在列表后面
                 // code
                 break;
                 
                 ++binCount;
             }
        }
        
        if (e != null) { 
        	// 修改原值~
        }
    }
}

Node内部类:像是一个链表的形式

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

1.4 get方法:

public V get(Object key) {
    HashMap.Node e;
    // 是根据输入节点的 hash 值和 key 值利用getNode 方法进行查找
    return (e = this.getNode(hash(key), key)) == null ? null : e.value;
}

final HashMap.Node<K, V> getNode(int hash, Object key) {
    HashMap.Node[] tab;
    HashMap.Node first;
    int n;
    // tab非空,长度>0,索引处非空
    if ((tab = this.table) != null && (n = tab.length) > 0 && (first = tab[n - 1 & hash]) != null) {
        Object k;
        // 检查头节点
        if (first.hash == hash && ((k = first.key) == key || key != null && key.equals(k))) {
            return first;
        }

        HashMap.Node e;
        if ((e = first.next) != null) {
            
            // 若定位到的节点是 TreeNode 节点,则在树中进行查找
            if (first instanceof HashMap.TreeNode) {
                return ((HashMap.TreeNode)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;
}

1.5 Resize方法

我们在扩容的时候,一般是把长度扩为原来2倍,所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。(左边为0不移动,左边为1移动)

0000 0000 0000 0000 0000 0000 0000 1111	     n = 16       
1111 1111 1111 1111 0000 1111 0000 0101                0101		5
1111 1111 1111 1111 0000 1111 0001 0101                0101		5

0000 0000 0000 0000 0000 0000 0001 1111	     n = 32
1111 1111 1111 1111 0000 1111 0000 0101               00101		5
1111 1111 1111 1111 0000 1111 0001 0101               10101		5 + 16

因此,我们在扩充HashMap的时候,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”

final HashMap.Node<K, V>[] resize() {
    HashMap.Node<K, V>[] oldTab = this.table; 			// 把旧的表装好
    int oldCap = oldTab == null ? 0 : oldTab.length;	        // 计算旧表的容量
    int oldThr = this.threshold;				// 计算旧表门限
    int newThr = 0;
    int newCap;
    if (oldCap > 0) {						// 原表非空的情况
        if (oldCap >= 1073741824) {			        // 超出去就不扩了
            this.threshold = 2147483647;
            return oldTab;
        }

        if ((newCap = oldCap << 1) < 1073741824 && oldCap >= 16) {
            newThr = oldThr << 1;
        }
    } else if (oldThr > 0) {				        // 原表是空(其它三个构造函数)
        newCap = oldThr;					// 容量设定为门限
    } else {
        newCap = 16;						// 空参构造函数创建的
        newThr = 12;
    }
    
    if (newThr == 0) {						// 设定门限
        float ft = (float)newCap * this.loadFactor;
        newThr = newCap < 1073741824 && ft < 1.07374182E9F ? (int)ft : 2147483647;
    }
    
    this.threshold = newThr;				        // 开始创建新表
    HashMap.Node<K, V>[] newTab = new HashMap.Node[newCap];
    this.table = newTab;
    if (oldTab != null) {
        for(int j = 0; j < oldCap; ++j) {
            // rehash开始
            HashMap.Node e;
            if ((e = oldTab[j]) != null) {
                // 原表置空
                oldTab[j] = null;
                if (e.next == null) {
                    // 原节点是单点,用它的hash值,重新计算索引
                    newTab[e.hash & newCap - 1] = e;
                } else if (e instanceof HashMap.TreeNode) {
                    // 红黑树rehash
                    ((HashMap.TreeNode)e).split(this, newTab, j, oldCap);
                } else {
                    // 链表rehash
                    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) {		// 扩2倍对应的值是0
                            if (loTail == null) {
                                loHead = e;
                            } else {
                                loTail.next = e;
                            }

                            loTail = e;				// loTail = loTail.next
                        } else {
                            if (hiTail == null) {
                                hiHead = e;
                            } else {
                                hiTail.next = e;
                            }

                            hiTail = e;
                        }
                        e = next;				// 遍历链表操作
                    } while(next != null);
                    
                    if (loTail != null) {			// 分别移动到新数组上
                        loTail.next = null;			// 低位直接复制
                        newTab[j] = loHead;
                    }

                    if (hiTail != null) {			// 高位 原索引+原容量
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    
    return newTab;
}
posted @ 2020-08-29 20:45  小小小南瓜  阅读(194)  评论(0编辑  收藏  举报