Java之HashMap进阶

HashMap是一种常见的Java集合,其内部由数组和链表(JDK1.8后还有红黑树)组成。在put过程中,HashMap通过hash算法确定key在数组中的位置,当达到特定阈值(默认容量的0.75)时会进行扩容。get操作则根据key的hash值找到对应的Entry链表并遍历查找。负载因子0.75是空间和时间效率的平衡选择,过高可能导致过多冲突,过低则浪费空间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对HashMap的数据结构和工作原理进行学习。

一、数据结构

随着版本的不同,HashMap的数据结构不同。

版本数据结构
jdk 1.7及以前数组+链表
jdk 1.8后数组+链表+红黑树

在这里插入图片描述

  • 数组
    HashMap的主干数据结构是数组,即key经过hash计算后将节点对象存放到数组对应的位置上
  • 链表
    当多个不同的key经过hash计算后得到的值相同,那么在数组对应位置上存储多个对象的链表,每个对象的属性除了key,value外还有next,即指向下一个节点的地址
  • 红黑树
    jdk1.8以后,如果某一条链表的长度超过8(默认),则转为红黑树的存储方式

二、工作原理

无参始化hashmap时,数组的初始容量16负载因子为0.75。容器的阈值等于 初始容量 × 负载因子,默认情况下阈值为 16 * 0.75 = 12;

2.1 put过程

第一步
hashMap使用自己的hash算法,算出传入的key的hash值;

第二步
用key的hash值调用indexOf()找到对象在数组中应该存放的位置;

第三步
当size(容器中已有Entry的数量)大于阈值时,进行扩容,将数组长度扩容为原来的2倍

第四步
将hash、key、value封装成一个Entry,存放到数组计算出来的位置中

  1. 位置无元素,放在该位置;
  2. 存在链表,则进行遍历,通过key进行equals比较:
    true:返回原来的value,将当前的value替换掉原来的
    false:将Entry对象的next指向改位置的初始节点(成为链表的第一个位置)

2.2 get过程

get过程的原理就比较简单,根据key的hashCode算出数组中的下标,遍历Entry链表,直到找到元素,源码如下:

/**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
     * key.equals(k))}, then this method returns {@code v}; otherwise
     * it returns {@code null}.  (There can be at most one such mapping.)
     *
     * <p>A return value of {@code null} does not <i>necessarily</i>
     * indicate that the map contains no mapping for the key; it's also
     * possible that the map explicitly maps the key to {@code null}.
     * The {@link #containsKey containsKey} operation may be used to
     * distinguish these two cases.
     *
     * @see #put(Object, Object)
     */
    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
 
        return null == entry ? null : entry.getValue();
    }
 
    /**
     * Offloaded version of get() to look up null keys.  Null keys map
     * to index 0.  This null case is split out into separate methods
     * for the sake of performance in the two most commonly used
     * operations (get and put), but incorporated with conditionals in
     * others.
     */
    private V getForNullKey() {
        if (size == 0) {
            return null;
        }
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }
/**
     * Returns the entry associated with the specified key in the
     * HashMap.  Returns null if the HashMap contains no mapping
     * for the key.
     */
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }
 
        int hash = (key == null) ? 0 : hash(key);
        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;
    }
  1. get的时候,如果key==null,判断Map的长度也为空的话就返回null,如果Map长度不为空,并且存在Entry,遍历table[0],返回e.value.
  2. getEntry的时候,首先要获取key的hash值,通过hash和table.length获取到的hashCode值得到entry在数组中存放的位置,如果传入的key与遍历的元素的key的hash相等的话并且key.equals为true,则返回entry;(如果返回的entry不为空的话,则返回entry的value值)。

2.3 负载因子

2.3.1 作用

用来计算阈值(初始容量 × 负载因子),当容量打到阈值时自动触发扩容机制。

2.3.2 为什么是0.75

这是时间和空间的权衡,是一个折中的选择。

  1. 负载因子太高,虽然提高了空间利用率,但是会导致大量的hash冲突,降低查询效率
  2. 当负载因子为0.5或者更低的时候,hash冲突降低从而提高了查询效率,但原本需要1M的存储空间,现在需要2M甚至更大的存储空间,结果就是空间利用率降低了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值