java——深入理解HashMap

HashMap

前沿:首先HashMap具体实现是由链表+数组的实现方式,并且采用了动态扩容技术

主要对象

table

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
注意是transient,非序列化
1.数组还有很多的空间没有被使用,没有被使用到的空间被序列化没有意义
2.不同的虚拟机对于相同 hashCode 产生的 Code 值可能是不一样的

Entry

Entry<K,V>是链表和数组的主要组成:
其重要的成员变量是Key,Value,Entry<K,V>next,hash

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;//存储指向下一个Entry的引用,单链表结构
    int hash;
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
  ```

构造如下:
在这里插入图片描述
其他组成:
transient int size;//实际个数
int capacity;//实际大小
int initialCapacity,//初始的数组大小
int threshold;//capacity*loadFactory。HashMap put新元素后,如果size>threhold,需要扩容

/**负载因子,代表了table的填充度有多少,默认是0.75
加载因子默认为0.75,也就是说大小为16的HashMap,到了第13个元素,就会扩容成32。
*/
final float loadFactor;

/*HashMap被改变的次数,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常ConcurrentModificationException/
transient int modCount;

HashMap方法:

1:构造方法
//主要是对传入的initialCapacity和loadFactor进行参数检验,没有为数组table分配内存空间而是在执行put操作的时候才真正构建table数组

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();//init方法在HashMap中没有实际实现,不过在其子类如 linkedHashMap中就会有对应实现
    }

2:put
主要分为四部分:
1:如果table为空,需要创建table
2:计算机hash值和数组的index
3:如果在数组中存在,则替换并返回旧hash
4:如果在数组中不存在,则直接插入数组

public V put(K key, V value) {
        //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,此时threshold为initialCapacity 默认是1<<4(16)==后面会改变==
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
       //如果key为null,存储位置为table[0]或table[0]的冲突链上
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);//获取在table中的实际位置
        
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        //如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
         modCount++;//保证并发访问时,若HashMap内部结构发生变化,快速响应失败
         
        //如果该对应数据不存在,直接加入
        addEntry(hash, key, value, i);//新增一个entry
        return null;
    }

其中使用的方法:
inflateTable(),hash(),indexFor(),recordAccess(),addEntry()

hash():转为hash值
addEntry():加入hash数组,并且根据情况进行扩容

如何初始扩容:

private void inflateTable(int toSize) {
        int capacity = roundUpToPowerOf2(toSize);//capacity一定是2的次幂
        /**此处为threshold赋值,取capacity*loadFactor和MAXIMUM_CAPACITY+1的最小值,
        capaticy一定不会超过MAXIMUM_CAPACITY,除非loadFactor大于1 */
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

如何根据hash确定索引:

static int indexFor(int h, int length) {
        return h & (length-1);
    }

关键点:为什么实际大小总是2的幂?
我的理解:
原因一:
因为index=hash%length-1
比如对于:
length=16 则length-1 =15 即 00001111
length=32 则length-1=31 即 00011111
可以看到,对于一个相同的hash,分别&两个不同的length-1,得到的结果result1,result2 差别就只在右数第5位,相当于扩容后只要修改一位就可以改变索引

原因二:
因为length=2^n,则length-1的位都是保持00001111111的形状,而hash&length-1中,高位不会产生影响,而低位任意一个变化变化都会产生影响,减少冲突的概率。
在h<length的情况下:
对于长度:23 length-1则比特为 10110,则对于结果result=10110
既可以是h=11110 10110 11111 10111 都对应相同的结果冲突增加
而对于长度32 length-1 则比特位11111 ,则对于结果10110
只有h=10110才可以与其对应

其实HashMap还要其他方法,但是感觉关键部分就是上面写的,如果还想加深了解,可以看一下

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值