HashMap

HashMap
实现Map接口

1.7 链表+数组,数组–Entry
1.8 链表+数组+红黑树,节点—Node

数组:存储区间连续,占用空间多,查找快
链表:查找效率低,删改快

线程不安全,
hashtable安全,但是效率低,因为里面都添加了synchronized

Concurrenthashmap线程安全,采用分段锁

Collections.SynchronizedMap也是另一种线程安全

Note:
JDK1.7之前会在多线程下发生死循环,是因为JDK1.7解决冲突的链表是使用头插法的
JDK1.8使用尾插法解决了上述问题

HashMap 成员变量(Member)
DEFAULT_INITIAL_CAPACITY:默认初始化容量大小(一般都是16),或者创建的时候可以自定义容量
Tips:最后初始化的容量都会是2的幂次(内部通过一堆位运算)

/**
 * Returns a power of two size for the given target capacity.
 */
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;
}

MAXIMUM_CAPACITY:最大的容量, 1<<30 (30次方),因为第一位JAVA是要存放符号位的,JAVA中由于桶的数量必须是2的幂次,而int的最大 最大数量是 (2<<31)-1,很明显不符合桶数量的要求,所以最大只能是 2<<30

**DEFAULT_LOAD_FACTOR:**默认的负载因子,一般是0.75f

TREEIFY_THRESHOLD: 一个桶中bin(箱子)的存储方式由链表转换成树的阈值。即当桶中bin的数量超过TREEIFY_THRESHOLD时使用树来代替链表。默认值是8.

UNTREEIFY_THRESHOLD: 执行resize操作时,当桶中bin的数量少于UNTREEIFY_THRESHOLD时使用链表来代替树。默认值是6

MIN_TREEIFY_CAPACITY: 当桶中的bin被树化时最小的hash表容量。(如果没有达到这个阈值,即hash表容量小于MIN_TREEIFY_CAPACITY,当桶中bin的数量太多时会执行resize扩容操作)这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。也就是根据默认来算64

Tips: 哈希表的链表树化成红黑树有 两个条件:

  1. 链表长度大于TREEIFY_THRESHOLD
  2. 哈希数组的长度大于MIN_TREEIFY_CAPACITY

HashMap成员方法(Method)

static final int tableSizeFor(int cap): 传进来的 table 值最好是2的N次方,如果传进来不是,会通过这个方法进行多次>>> 最终得到一个大于输入参数且最近的2的整数次幂的数

**final Node<K,V>[] resize()😗*1.8 的resize,把创建table和扩容table都放到了一起,
先判断是否有oldTable,还有拿到old threshold 临界值
新的就初始化,threshold 是 DEFAULT_INITIAL_CAPACITY初始化容量 * DEFAULT_LOAD_FACTOR 加载因子

public V put(K key, V value)
实际调用 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)

  1. 先判断当前table是否为空,如为空则调用resize(),创建一个table,判断边界值(threshold),初始边界值为0,赋值给oldThr(旧边界值),然后计算新的边界值

  2. 根据计算出来的hash值,与刚创建的table长度,进行 & (按位与) 运算 , if null 则创建一个新节点
    if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);

  3. 否则与原有元素判断 hash 是否相等,然后判断key 是否相等,都不相同,key不同,则判断是否是红黑树结构,如果相同,new value 覆盖 old value

if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
p = tab[i = (n - 1) & hash]

Note:不同的对象计算出来的hash值可能相同可能不同;相等就发生hash冲突,key地址判断,不同则判断内容是否相同
4. 如果发生hash冲突,往链表上查找是否有相同如没有则放上去;最后判断链表长度,查看是否满足条件转换红黑树,break
5. 如不是红黑树结构,将新数据放入到 p.next(尾插法), JDK1.7 是头插法

static final int hash(Object key): (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) 提供数组下标,右移16位为的是更均匀的获得数组下标,提高了高位参与运算的数量,散列会更加随机.

Note:
由于和(length-1)运算,length 绝大多数情况小于2的16次方。所以始终是hashcode 的低16位(甚至更低)参与运算。要是高16位也参与运算,会让得到的下标更加散列。

所以这样高16位是用不到的,如何让高16也参与运算呢。所以才有hash(Object key)方法。让他的hashCode()和自己的高16位^运算。所以(h >>> 16)得到他的高16位与hashCode()进行^运算。

Question:为什么用^而不用&和|
因为&和|都会使得结果偏向0或者1 ,并不是均匀的概念,所以用^.

转化红黑树
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;
    }
    if (e.hash == hash &&
        ((k = e.key) == key || (key != null && key.equals(k))))
        break;
    p = e;
}

从0遍历节点,binCount计算个数 ,当大于8时
进入this method
final void treeifyBin(Node<K,V>[] tab, int hash):
里面会经过这个判断: if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
数组长度小于MIN_TREEIFY_CAPACITY,会进行扩容,即使链表大于8

Resize():扩容是2*旧容量来进行扩容,扩容后会重新计算hash值

红黑树转变
TreeNode<K,V> hd = null, tl = null; hd是红黑树头节点 ,tl是红黑树尾结点

Note:该方法还未转为红黑树,只是设置好了节点而已

if ((tab[index] = hd) != null)
    hd.treeify(tab);
final void treeify(Node<K,V>[] tab):This method开始转变为红黑树

说明: Forms tree of the nodes linked from this node.

HashMap扩容机制
When: HashMap中的元素个数超过大小Capacity(数组长度)*loadFactor(负载因子) 就会进行扩容 (扩容异常消耗性能 –因为每次扩容都要重新进行hash分配);

Rehash方式: 2倍原数组长度,便且重新排列,通过

hash扩容后,因为是2倍原数组长度,
所以就是相当于参与计算索引&运算的时候,
会在高位多出一位进行运算,所以在扩容后重新计算hash值的时候,该数据存放的位置要不就是原位置,要不就是 原位置+原数组长度的位置,取决于重新hash后,首位是0还是1的区别,0原位置,1就一倍旧长度位置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值