HashMap源码分析(JDK1.7)

8 篇文章 0 订阅

HashMap源码分析(JDK1.7)

构造方法

public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

 public HashMap(int initialCapacity, float loadFactor) {
     //判断初始化容量
     if (initialCapacity < 0)
         throw new IllegalArgumentException("Illegal initial capacity: " +
                                            initialCapacity);
     //判断是否大于最大容量,如果大于让他等于最大容量
     if (initialCapacity > MAXIMUM_CAPACITY)
         initialCapacity = MAXIMUM_CAPACITY;
     if (loadFactor <= 0 || Float.isNaN(loadFactor))
         throw new IllegalArgumentException("Illegal load factor: " +
                                            loadFactor);

     this.loadFactor = loadFactor;
     threshold = initialCapacity;
     init();
}

public HashMap(int initialCapacity, float loadFactor) {
    //判断传入的初始化大小是否小于0如果小于0就报异常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    //判断如果初始化大小大于阈值,就让他对于最大阈值
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //如果=0 活着 传入的增长因子是否是一个是一个数字 如果不成立抛出异常
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    //赋值增加因子
    this.loadFactor = loadFactor;
    //初始化大小,因为需要传入的初始大小是二的次幂所以需要以下方法解决
    this.threshold = tableSizeFor(initialCapacity);
}
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;
}

假设传入的cap为38,当然这个数可以是

经过第一行可以得出n为37

第二行:0010 0101>>>1 得到0001 0010 在进行|运算0011 0111

第三行:0011 0111>>>2 得到0000 1101 在进行|运算0011 1111

第四行:0011 1111>>>4 得到0000 0011在进行|运算0011 1111

第五行:0011 1111>>>8 得到0000 0000 在进行|运算0011 1111

第六行:0011 1111>>>16 得到0000 0000 在进行|运算0011 1111

经过上面真么多行的位移我们可以保证最高位1后面的二进制全部为1了

然后(n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

如果n小于0那就直接返回1,因为2的最小次幂就是1,如果继续判断n是否大于最高1<<30,如果大于让他使用最大容量,负责让他等于n+1;为什么等于n+1

0011 1111 +1 就等于0100 0000 ,二的次幂有一个特点那就是二进制里面只能出现一个1,=

然后我们可以想想为什么第一次要减1,如果这个数就是二的次幂如整数32,二进制位00100000

不执行第一行,执行第二行到第六行可以得到 0011 1111 在加1

不就是0100 0000了吗得到64,他自己本身就是二的次幂直接拿来用就可以了,无需对他进行操作

所以我们让他减少1抹掉自己最高位的1 得到二进制0001 1111

经过第二行到第六行运算得到 0001 1111 在经过+1运算有得到原来的0100 0000了


如果不传入参数使用的都是默认参数,到此容量和加载因子初始化结束

Put方法分析

public V put(K key, V value) {
    //判断是否为空 直接插入到下标为0的数组中
    if (key == null)
        return putForNullKey(value);
    //对key进行计算hash值
    int hash = hash(key);
    //计算下标 内部做了 h & (length-1);  这样& 只有当2边为1才为1 不管hash值有多长只取length-1位
    //计算hash值是 1111111111 length如果是8  0111 最高也就 0111 下标为7不会越界
    int i = indexFor(hash, table.length);
    //进行for循环 
    //首相对e 进行复制  获取数组的的计算的i的下标位置 是否为空,如果不为空就进行循环
    //Entry是一个链表,所以调用next一直循环,直到找到对应的值,或者循环到最后一个节点
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        // 首先对hash值进行对比如果相同在对比key的地址是否相同一般做基本数据类型比较,如果是对象调用equals进行对比,如果查询到内部有相同的key就进入
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            //保存旧的值
            V oldValue = e.value;
            //把新的值赋值进入
            e.value = value;
            e.recordAccess(this);
            //返回旧的值
            return oldValue;
        }
    }
//如果没有寻找到
    modCount++;
    //直接使用头插法插入一条数据
    addEntry(hash, key, value, i);
    return null;
}

Hash值计算

final int hash(Object k) {
    int h = 0;
    if (useAltHashing) {
        if (k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h = hashSeed;
    }

    h ^= k.hashCode();
    //主要目的是让hashCode的全部二进制参与到计算中
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
void addEntry(int hash, K key, V value, int bucketIndex) {
    //threshold 默认16*0.75
    //首先会判断size是否大于等于阈值以及table当前下标是不为空 如果符合条件进行扩容
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

void createEntry(int hash, K key, V value, int bucketIndex) {
    //首先使用一个变量保存头结点的对象
    Entry<K,V> e = table[bucketIndex];
    //把头结点等于现在的插入的对象 让他的下一个结点等于上次的头结点
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    //在对size进行++
    size++;
}

当一个新数据要插入的时候 如何计算的下标和数据1是同一个,他会先循环遍历是否有相同的key如果有就替换,如果没有进行插入

在这里插入图片描述

首先让一个变量先保存数组[i]的值
在这里插入图片描述

在让新的数据的下一个结点等于这个变量

在这里插入图片描述

最后把数组中的值替换成新数据的地址值

在这里插入图片描述

经过以上几步实现了新数据的插入

resize

void resize(int newCapacity) {
    //把旧的数组保存起来
    Entry[] oldTable = table;
    //把旧的数组长度保存下来
    int oldCapacity = oldTable.length;
    //判断旧的数组长度是否等于最大阈值 如果等于把阈值增加到整形最大值
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
	//创建一个新的数组也就是length*2
    Entry[] newTable = new Entry[newCapacity];
    
    boolean oldAltHashing = useAltHashing;
    useAltHashing |= sun.misc.VM.isBooted() &&
        (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    boolean rehash = oldAltHashing ^ useAltHashing;
    transfer(newTable, rehash);
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    //遍历每一个元素 包括数组内部的链表
    for (Entry<K,V> e : table) {
        while(null != e) {
            //使用next保存下一个元素的地址
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //重新生成下标,因为计算下标是通过h & (length-1); 数组长度发生了改变所以要存放的下标也会改变
           //只不过这个改变是有规律的, length长度无法就是*2,
            // 如:0001 0100 & (16-1)  得4
            // 如:0001 0100 & (32-1)  的20  可以看出增加了16 也就是扩大数组容量的大小
            // 如:0100 0011 & (16-1)  得3
            // 如:0100 0011  & (32-1)  的3  可以看到没有发生改变,这是因为参数的hash值和(32-1)的二进制最高位1的位置没有1 所以不会二进制进1操作
            //通过以上可以看出转移数据只会出现在他原来下标的位置或者他的 +原长度的下标位置
            int i = indexFor(e.hash, newCapacity);
            // 然后进行头插法 ,我们可以通过以下几张图片看出插入的变化
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

首先创建一个新的数组长度是原来的2倍

在这里插入图片描述

假设已经遍历到下标1的数组的第一个元素,切第一个元素并没有受到长度变化改变下标

在这里插入图片描述

可以得到

在这里插入图片描述

继续经过上面步骤 next下移 ,直到无法寻找到元素,可以看出因为使用了头插法链表发生了反转的操作.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-448LeqTS-1630207181207)(HashMap源码分析(JDK1.7)].assets/1630206893961.png)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值