杂文(6):HashMap总结

提示:这篇文章由于我懒癌犯了因此有很多内容都是不全的,文章的最后有参考文章部分,可以结合着看。
最后更新于2021年3月3日08:58:49

HashMap

HashMap是基于哈希表的Map接口的实现,允许使用null值和null键。HashMap是非线程安全的,也就是说在多个线程同时对HashMap中的某个元素进行增删改操作的时候,是不能保证数据的一致性的。

HashMap的底层基于数组和链表实现。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。

HashMap中计算Hash值:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        //若key=null则键值对存入索引为0的桶
        //否则计算key的hashcode并将其右移16位做异或运算得到索引值(即hash值)。
        //右移是为了减少碰撞防止hash冲突。
        //异或运算能够尽量保留各部分特征
    }

HashMap底层通过链表解决hash冲突,如图
在这里插入图片描述

hashmap内部结构

HashMap以键值对形式储存数据,每个键值对都存储在Entry<K,V>这样的对象中。

static class Entry<K,V> implements Map.Entry<K,V> 
{
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
 
        //Some methods are defined here
}

hashmap关键属性

transient Entry[] table;//存储元素的实体数组

transient int size;//存放元素的个数

int threshold; //临界值 当实际大小超过临界值时,会进行扩容threshold = 加载因子*容量

final float loadFactor; //加载因子

transient int modCount;//被修改的次数

若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.链表长度会越来越长,查找效率降低。反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)

hashmap的put

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            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++;
        addEntry(hash, key, value, i);
        return null;
    }

在这里插入图片描述
步骤:
1.空key放到table[0]位置
2.若不是空key,计算键对应的哈希值
3.indexFor()方法返回特定键值对在table中的索引
4.查找该索引下的链表中是否有相同的key,是的话,用心值取代旧值;不是的话,加入该键值对到链表末尾。

hashmap的扩容(这个被问到了,看一下)

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
       }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);//用来将原先table的元素全部移到newTable里面
        table = newTable;  //再将newTable赋值给table
        threshold = (int)(newCapacity * loadFactor);//重新计算临界值
    }

新建了一个HashMap的底层数组,上面代码中第10行为调用transfer方法,将HashMap的全部元素添加到新的HashMap中,并重新计算元素在新的数组中的索引位置。

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

当HashMap中的元素个数超过数组大小乘以loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过 16乘0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,扩容是需要进行数组复制的,复制数组是非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
hashmap扩容涉及到元素迁移:

JAVA8中的元素迁移:

这是java8的resize函数注释:Initializes or doubles table size. If null, allocates in accord with initial capacity target held in field threshold. Otherwise, because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move with a power of two offset in the new table. 初始化或翻倍表格大小,如果表格为空,根据字段阈值中的初始容量目标分配大小。否则,由于我们根据二的幂次扩张,每个bin中的元素要么留在原index,要么在新表中做一个二的幂次的偏移。

HashMap的扩容机制

hashmap的get

public V get(Object key) {
    if (key == null)
    return getForNullKey();
    int hash = hash(key.hashCode());
    for (Entry<K , V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }
    return null;
}

1.空key?是的话调用getForNullKey()方法。
2.不是空key,计算对应的hash值。
3.indexFor()方法找到对应索引。
4.对应索引上找相同的key并返回value,无相同key则返回null。

jdk1.8新增

链表转化为红黑树

链表的长度不小于8,而且数组的长度不小于64的时候才会将链表转化为红黑树

put方法

1.若发生了hash冲突,还要判断此时的数据结构是什么
2.若此时的数据结构是红黑树,那就直接插入红黑树中
3.若此时的数据结构是链表,判断插入之后是否大于等于8
4.插入之后大于8了,就要先调整为红黑树,再插入
5.插入之后不大于8,那么就直接插入到链表尾部即可。

小知识

异或运算:将十进制转成二进制后比较每一位,相同为0,不同为1。例如15^2=13.
红黑树:红黑树是一个自平衡的二叉查找树,查找效率会从链表的o(n)降低为o(logn)。

参考:
你能谈谈HashMap怎样解决hash冲突吗
How HashMap Works Internally In Java?
idea版Java就业班
HashMap源码分析(jdk1.8,保证你能看懂)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值