HashMap底层实现原理

JDK1.6,1.7中,HashMap采用数组+链表实现,用链表处理冲突,同一hash值的链表都存储在一个链表里。当位于同一链表中的元素较多时,通过Key值依次查找的效率较低。当有大量元素都存放到同一个数组元素时,这个数组元素下有一条长长的链表,假设链表有N个元素,遍历的时间复杂度为O(n),完全失去了它的优势。
JDK1.8中,HashMap采用数组+链表+红黑树(查找时间复杂度为O(logn))实现,当链表长度超过阈值8时,将链表转换为红黑树,这样可以减少查找时间。
这里写图片描述

HashMap的扩容
如果不指名初始化大小,默认大小为16,如果数组中的元素达到(填充比*Node.length)重新调整HashMap大小变为原来的2倍大小。
当链表数组的容量超过初始容量的0.75时,将链表数组扩大2倍,把原链表数组复制到新数组中。
问题:为什么需要填充比?为什么需要扩容?
如果填充比很大,说明利用的空间很多,如果不进行扩容的话,链表就会越来越长,这样查找的效率很低。
HashMap是以空间换时间,所以填充比没必要太大。填充比太小又会导致空间浪费。如果注重内存,填充比可以稍大,如果注重查找性能,填充比可以稍小。
HashMap实现原理
HashMap的主干是一个Entry数组,Entry是HashMap的基本单元,每一个Entry包含一个key-value键值对。
HashMap由数组+链表组成,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在。
HashMap的put操作

 public V put(K key, V value) {
        //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,此时threshold为initialCapacity 默认是1<<4(24=16)
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
       //如果key为null,存储位置为table[0]或table[0]的冲突链上
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);//对key的hashcode进一步计算,确保散列均匀
        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;
    }    

这里写图片描述

HashMap的get操作

public V get(Object key) {
     //如果key为null,则直接去table[0]处去检索即可。
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
 }
final Entry<K,V> getEntry(Object key) {

        if (size == 0) {
            return null;
        }
        //通过key的hashcode值计算hash值
        int hash = (key == null) ? 0 : hash(key);
        //indexFor (hash&length-1) 获取最终数组索引,然后遍历链表,通过equals方法比对找出对应记录
        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 != null && key.equals(k))))
                return e;
        }
        return null;
    }    

get方法实现,key(hashcode)–>hash–>indexFor()–>最终索引位置,找到相应位置的table[i],再查看是否有链表,遍历链表,通过key的equals方法比对查找对应的记录。
重写equals方法需同时重写hashCode方法
我们在进行get和put时,使用的key从逻辑上讲是等值的(通过equals方法),但如果没有重写hashCode方法,所以put操作时,key(hashcode1)–>hash–>indexFor–>而通过key取出value的时候 key(hashcode1)–>hash–>indexFor–>最终索引位置,由于hashcode1不等于hashcode2,导致没有定位到一个数组位置而返回逻辑上错误的值null。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值