HashMap Reading (jdk 8 version)
基本参数
- DEFAULT_INITIAL_CAPACITY = 1 << 4 (1 * 2^4) 默认的初始大小16 MUST be a power of two, 原因是 - 1后 每一位都是1, 后面有用
- MAXIMUM_CAPACITY = 1 << 30 最大能容纳这么多 所以并非无限大
- DEFAULT_LOAD_FACOTR = 0.75f
- TREEIFY_THRESHOLD = 8, 当一个hash value的bucket后面list长度 > 8时,新加入一个元素到list,这个list会变成1棵树,方便快速查询
- UNTREEIFY_THRESHOLD = 6, Resize的时候,如果一个hash value的bucket后面的list长度 < 6,不懂
- MIN_TREEIFY_CAPACITY = 不懂
- transient Node<K,V>[] table; 数组
- transient Set<Map.Entry<K,V>> entrySet;
- transient int size;
- transient int modCount;
- int threshold; 当size达到这个threshold时,便会发生resize (= capacity * load factor) 不是size if(s > threshold) resize()
- final float loadFactor;
Constructors
- HashMap(int initialCapacity, float loadFactor)
- HashMap() 在构造函数阶段,table = null, 只在第一次调用put方法的时候才会initialize table of Size DEFAULT_INITIAL_CAPACITY
实现
Node<K,V> {
int hash;
K key;
V value;
Node<K,V> next;
}
Methods
- V get(Object key) -> 调用底层的getNode(hash(key), key)获得
- Node<K,V> getNode(int hash, Object key)
- (hash & table.size - 1) 得到index
- table[index]为第一个元素first,对比第一个元素的key和传入的key是否equal, 是则返回
- 若不是, 则看first的next是否是TreeNode类型,若是,则TraverseTree
- 若不是, 则iterate over the list
- containsKey(Object key) -> 调用底层的getNode(hash(key), key)获得
- V put(K key, V value) -> 调用底层的V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)实现
- V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
- 这个方法有两个功能,若map里不包含这个key,则将key, value插入,若map里已经有了这个key,则找到这个key对应的entry,覆盖entry的value
- 如果是list的情况下,新的Entry被插入到list的最后,决定是否要树化list Treeify
- 然后更新size,更新modCount,并决定是否要resize()
Treeify是对list而言的 而resize是对table而言的
- Node<K,V> resize
每次扩容,都是在原有table的基础上*2 tableSize != size, tableSize = tableSizeFor(size)
resize不会无限扩容,当到达MAXIMUM_CAPACITY = 1 << 30后便不会进行,放不下了但是并不会报错,抛异常
resize的过程
- 当前的tableSize 记为 oldCapacity, 则newCapacity = 2 * oldCapacity (= oldCapacity << 1)
- Iterate Over table, 对每一个table Item 和table Item List元素, 计算其在新的table下的index
- Prelimilary:
- a. 在HashMap中, capacity(既tableSize)一定是2的幂,则一定可以表示成 00…01000…000的形式
- b. 则cap - 1一定可以表示成 00…00111…111的形式
- c. 任何hash & capacity只会产生两种结果, 1. capacity 2. 0
- capacity的二进制表示中只有1个1,其余位置全为0,hash在capacity为1的位置上,只有两种可能,为1或者不为1,则hash & capacity后,结果这个位置上可能为1也可能为0
- capacity的二进制表示中只有1个1,其余位置全为0,抛开capacity为1的这个位置,其余位置全为0,0和hash相应位置上的任何值&都为0
- 所以hash在capacity为1的位置上也正好为1,则hash & capacity = capacity
- 所以hash在capacity为1的位置上为0,则hash & capacity = 0
- d. 当capacity为2的幂时, hash & (capacity - 1) === hash % capacity,只是位操作更快捷(当x = 2 ^ n(n为自然数)时,a % x = a & (x - 1), https://blog.csdn.net/jiangyupeng/article/details/84918009)
- e. index为i的table元素以及其后续list的所有元素,其hash可以归纳为 i, i+capacity, i+2capacity, i+3capacity…,i+n*capacity
- f. 当newCapacity = 2 * oldCapacity时, i, i+capacity, i+2capacity, i+3capacity…,i+ncapacity 可以看成 i, i + capacity, i + newCapacity, i + newCapacity + capacity, … i + nnewCapacity + capacity
- g. 所以扩容后,其对应的新的下标为: i, i + capacity, i, i + capacity, i … (注意是capacity 而不是newCapacity),总之,扩容后只有两个去处,下标i 或下标 i+capacity处
- h. 谁去i?谁去i+capacity? i & capacity = 0, (i + capacity) & capacity = capacity, (i + newCapacity) & capacity = 0, (i + newCapacity + capacity) & capacity = capacity…
- i. 所以只需要hash & capacity 看是否等于0,便知道了, 若为0则去i, 若为capacity则去 i + capacity
- Summary: 同一个table index下list的元素的新index = i 或者 i + capacity; Iterate over的时候,可以用两个新的list来记录去到不同位置的元素,旧list的元素总是查到其中一个新list的尾部
-
Treeify
-
int hash(Object key): 计算一个key的hashValue, key == null ? 0 : (h = key.hashCode()) ^ (h >>> 16)
- HashMap中可以插入key为null的Entry,放在第一位
- 计算hashValue的方法
a. key.hashCode()为32位,则h>>>16,则是把h的前16位移动到后16位的位置,前16位用0代替, 任何数和0异或都为本身, 则前16位不变
b. 则有可能会改变的就是后16位
c. 实质上用前16位和后16位异或,得到的便是最后的hash值为何要把前16位和后16位异或
- 根据hashCode计算数组的下标为 hashCode & (数组length - 1)
- 数组length一般不大,比如32, 则数组length -1 = 011111 只有最后5位为1,其余32-5=27位全为0
- 若hashCode的前16位和后16位不异或,那在计算的index的时候,实际上极大可能只有最后的几位,这里是5位hashCode参与了运算,以为其余位上的hashCode和0&后都为0
- 所以 前16位和后16位hashCode异或,便是为了让hashCode的前面部分也能参与到运算中
- 目的是为了最后的index减少碰撞,但是真的效果如何,未知.
- tableSizeFor(int cap): 确定index数组的实际大小,cap为index数组的容纳能力.该函数功能是返回一个比给定整数大且最接近的2的幂次方整数int类型,如给定10,返回2的4次方16.
- int n = cap - 1; //
- n |= n >>> 1; // n >>> 1 将n向右移动1位,则最后1位移出,最高位变0,次高位变为最高位 01?? -> 001?, n |= n >>> 1 01?? | 001? = 011?
- n |= n >>> 2;
- n |= n >>> 4;
- n |= n >>> 8;
- n |= n >>> 16;
- return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; //
- 综上的操作,结果就是将类似0…1XXX … XXXX的树变成0…1111 … 1111.即最大能达到32位1.因为Integer为32位 所以max则是第一位为1,则他只需要移动16位,就可以变成01111 … 1111