HashMap源码读一读

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)
  1. (hash & table.size - 1) 得到index
  2. table[index]为第一个元素first,对比第一个元素的key和传入的key是否equal, 是则返回
  3. 若不是, 则看first的next是否是TreeNode类型,若是,则TraverseTree
  4. 若不是, 则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)
  1. 这个方法有两个功能,若map里不包含这个key,则将key, value插入,若map里已经有了这个key,则找到这个key对应的entry,覆盖entry的value
  2. 如果是list的情况下,新的Entry被插入到list的最后,决定是否要树化list Treeify
  3. 然后更新size,更新modCount,并决定是否要resize()
    Treeify是对list而言的 而resize是对table而言的
  • Node<K,V> resize

每次扩容,都是在原有table的基础上*2 tableSize != size, tableSize = tableSizeFor(size)
resize不会无限扩容,当到达MAXIMUM_CAPACITY = 1 << 30后便不会进行,放不下了但是并不会报错,抛异常
resize的过程

  1. 当前的tableSize 记为 oldCapacity, 则newCapacity = 2 * oldCapacity (= oldCapacity << 1)
  2. 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
  1. capacity的二进制表示中只有1个1,其余位置全为0,hash在capacity为1的位置上,只有两种可能,为1或者不为1,则hash & capacity后,结果这个位置上可能为1也可能为0
  2. capacity的二进制表示中只有1个1,其余位置全为0,抛开capacity为1的这个位置,其余位置全为0,0和hash相应位置上的任何值&都为0
  3. 所以hash在capacity为1的位置上也正好为1,则hash & capacity = capacity
  4. 所以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)

  1. HashMap中可以插入key为null的Entry,放在第一位
  2. 计算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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值