HashMap源码简单分析

说明:本次源码分析只分析到链表没树化之前

1.8
数组+链表+红黑树
哈希表:数组,数组下放存放链表or红黑树

Node<K,V>[] table;

成员变量: hash记录hashCode ,key记录键,value ,next 下一个节点

final int hash;
final K key;
V value;
Node<K,V> next;

1.初始容量必须为2的n次方(原因:方便进行&运算、方便扩容之后数据移动),后面会说明的
默认为16

 public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    

2.new 不会创建数组,第一次put的时候才创建,hashmap初始化并没有初始化table

3.常量:

默认容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
链表转化成红黑树的阈值,当链表节点大于等于8,并且表长度大于等于64才会树化
static final int TREEIFY_THRESHOLD = 8;
结合上一个阈值,判断树化条件之二
static final int MIN_TREEIFY_CAPACITY = 64;
红黑树退化成链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
当前 HashMap 所能容纳键值对数量的最大值,超过这个值,则需扩容
下一次扩容的阈值
The next size value at which to resize (capacity * load factor).
int threshold;
加载因子
final float loadFactor;
HashMap被修改的次数,由于HashMap非线程安全,在对HashMap进行迭代时,
如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作)需要抛出异常ConcurrentModificationException
transient int modCount;
key的个数
transient int size;

分析hashMap的成员变量过程中, threshold并没有默认值
如果使用带参数(默认容量的情况下),阈值会被初始化成第一个大于
等于cap 2的n次幂,第一次插入,原表为null,需要扩容,这样就能保证容量一定是2的n次幂

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

4.put操作
第一步 先计算hash值 高位向低位移动16位
为了能让 HashMap 存取⾼效,尽量少碰撞,也就是要尽量把数据分配均匀。
获取Object默认的hashcode,将达hashcode跟向右移动16位的hashcode进行异或运算。为什么?答:异或运算的结果大小值比较均匀分散
一般称为扰动函数

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

第二步:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict)

然后:
数组为空,则初始化调用resize()函数

Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;

然后插入有多种情况
情况1:表的位置为空
数组容量跟hash值&运算,如果这个位置还是空的,直接放入数据
其实就%运算
(容量必须2的n次方,因为只有这样才能保证用&运算可以代替%)

if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);

情况2:哈希冲突
2.1: 哈希值相等、key也相等

    if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;

也就是刚好头节的key完全跟这个key一致,判断是先判断hash值,
hash值一样,再判断key是否相等,也就是说hash值一样,也不能保证
key一定相等。这也就是重写equals方法的同时也一定要重写hashcode方法(扩展)
改变value值即可
2.2:判断是不是树形结构

else if (p instanceof TreeNode)
    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
   红黑树添加操作

2.3:哈希冲突,添加或者替换

else {
    for (int binCount = 0; ; ++binCount) {
        if ((e = p.next) == null) {
            p.next = newNode(hash, key, value, null);
            if (binCount >= TREEIFY_THRESHOLD - 1) // 长度等于阈值就去尝试树化
                treeifyBin(tab, hash);
            break;
        }
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            break;
        p = e;
    }
}

treeifyBin(tab, hash);这个操作链表树化(不一定,前面说过树化又两个判断条件)

那它是如何进行判断的呢?点进这个方法看看(下方代码块)

这里第一个if判断,判断当前哈希表长度(默认长度大于等于64树化)是否满足树化要求

   final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)//不满足则对哈希表扩容
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

5.真正的扩容

Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length; //获取原来哈希表的长度
int oldThr = threshold;//原来的加载因子(默认0.75)*当前容量
int newCap, newThr = 0;
if (oldCap > 0) {     //如果不是新建的
    if (oldCap >= MAXIMUM_CAPACITY) {   //判断是否大于最大的容量 1<<30
        threshold = Integer.MAX_VALUE;  //一般都是不大于的
        return oldTab;
    }
    else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&  //判断扩容是否
             oldCap >= DEFAULT_INITIAL_CAPACITY)           //小于最大容量
        newThr = oldThr << 1; // double threshold          //原来的容量大于
}                                                          //默认的容量


 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //扩容的newTab
table = newTab;
if (oldTab != null) { //如果原哈希表还有数据,我们要进行数据迁移
    for (int j = 0; j < oldCap; ++j) {  //遍历哈希表
        Node<K,V> e;
        if ((e = oldTab[j]) != null) {//把哈希表的值赋值给e
            oldTab[j] = null;   //直接赋null给旧哈希表,估计是垃圾回收机制
            if (e.next == null)  //如果next值为空,说明链表只有一个节点
                newTab[e.hash & (newCap - 1)] = e;
            else if (e instanceof TreeNode) //是树结构,红黑树进行操作
                ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
            else { // preserve order        说明next值还有
                Node<K,V> loHead = null, loTail = null; //不变的哈希位置
                Node<K,V> hiHead = null, hiTail = null; //改变的hash位置
                Node<K,V> next;
                do {
                    next = e.next; //下一个节点
                    if ((e.hash & oldCap) == 0) { //哈希表长度2的*次方的
                        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;
                }
            }
        }
    }
}

第一次利用2的 * 次方进行&运算

if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);

假设原哈希某一位置放置了n个元素
例如1,17两个key的哈希值后4位一定是相等
(补充数值的高16位跟低16位进行^之后 还是等于本身)
17: 10001
1: 00001
16的二进制位:10000
进行&运算,只需要看最高一位
最高位等于1 ,位置等于原位置+原哈希表的容量
最高位等于0,位置不变

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值