0
点赞
收藏
分享

微信扫一扫

对HashMap的一些汇总

hwwjian 2022-01-26 阅读 85
数据结构

文章目录


前言

最近复习到JAVA的HashMap这一块,也算是比较重要的内容;写个博客记录一下学习过程中的一些理解。
参考文档:
https://blog.csdn.net/weixin_35586546/article/details/81153793
https://blog.csdn.net/weixin_39436556/article/details/118635569
https://blog.csdn.net/shenmofeng15/article/details/84134117


一、HashMap的数据结构和工作原理

1.数据结构

在JDK7 : HashMap 是由 数组,链表 组成的;
在JDK8: HashMap 是由 数组,链表,红黑树 组成的;
其中:链表的节点存储的是一个 Entry 对象,每个Entry 对象存储四个属性(hash,key,value,next)
整体是一个数组;
数组每个位置是一个链表;
链表每个节点中的Value即我们存储的Object;

2.工作原理

以put方法来举例:
第一步:通过 HashMap 自己提供的hash 算法算出当前 key 的hash 值

第二步:通过计算出的hash 值去调用 indexFor 方法计算当前对象应该存储在数组的几号位置

第三步:判断size 是否已经达到了当前阈值,如果没有,继续;如果已经达到阈值,则先进行数组扩容,将数组长度扩容为原来的2倍。
第四步:将当前对应的 hash,key,value封装成一个 Entry,去数组中查找当前位置有没有元素,如果没有,放在这个位置上;如果此位置上已经存在链表,那么遍历链表,如果链表上某个节点的 key 与当前key 进行 equals 比较后结果为 true,则把原来节点上的value 返回,将当前新的 value替换掉原来的value,如果遍历完链表,没有找到key 与当前 key equals为 true的,就把刚才封装的新的 Entry中next 指向当前链表的始节点,也就是说当前节点现在在链表的第一个位置,简单来说即,先来的往后退。

这里有一个阈值的概念;HashMap提供了有参构造和无参构造,无参构造中,容器默认的数组大小 initialCapacity 为 16,加载因子loadFactor 为0.75。容器的阈值为 initialCapacity * loadFactor,默认情况下阈值为 16 * 0.75 = 12。

3.扩容

threshold = (int)( capacity * loadFactor );

阈值 = 容量 X 负载因子;

初始容量默认为16,负载因子(loadFactor)默认是0.75; map扩容后,要重新计算阈值;当元素个数 大于新的阈值时,map再自动扩容;以默认值为例,阈值=16*0.75=12,当元素个数大于12时就要扩容;那剩下的4个数组位置还没有放置对象就要扩容,造成空间浪费,所以要进行时间和空间的折中考虑;

loadFactor过大时,map内的数组使用率高了,内部极有可能形成Entry链,影响查找速度;

loadFactor过小时,map内的数组使用率较低,不过内部不会生成Entry链,或者生成的Entry链很短,由此提高了查找速度,不过会占用更多的内存;所以可以根据实际硬件环境和程序的运行状态来调节loadFactor;


二、底层实现

HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。

初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍。
说的通俗一点,比如说你要装水 你首先找个一个桶 这个桶的容量就是加载容量,加载因子就是比如说你要控制在这个桶中的水要不超过水桶容量的多少,比如加载因子是0.75 那么在装水的时候这个桶最多能装到3/4 处, 这么已定义的话 你的桶就最多能装水 = 桶的容量 * 加载因子
如果桶的容量是40 加载因子是0.75 那么你的桶最多能装40*0.75 = 30的水
如果你要装的水比30 多 那么就该用大一点的桶 而rehash就是负责增加桶的容量的方法。

HashMap通过计算key的hash值。当hash值相同时,就会出现hash冲突,HashMap通过链表来解决冲突。

2.代码举例

put方法分析

//添加键值对方法
public V put(K key, V value) {
        //如果key为null,则hash值为0,将这个entry放在索引为0的地方,即table[0]
        if (key == null)
            return putForNullKey(value);
        //key不为null
        int hash = hash(key.hashCode());//计算key的hashCode的hash值
        int i = indexFor(hash, table.length);//返回hash值所在的索引位置
        //遍历table[i]处的Entry,若存在key相同并且hash值相同,则用新元素替换旧元素
        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;
            }
        }
       //修改标记+1,然后进行元素添加操作
        modCount++;
        addEntry(hash, key, value, i);//将包含特定key、value和hash值的entry添加到特定的桶中
        return null;
    }
 
    //添加key为null的元素
    private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
    /**
     * Adds a new entry with the specified key, value and hash code to
     * the specified bucket.  It is the responsibility of this
     * method to resize the table if appropriate.
     *
     * Subclass overrides this to alter the behavior of put method.
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

get()方法分析

/**
* 返回映射的key对应的value值,若map不包含该key,返回null
*/
public V get(Object key) {
        if (key == null)//获取key为null的value值
            return getForNullKey();
        int hash = hash(key.hashCode());//计算key的hashCode的hash值
        //遍历数组中对应hash值的Entry链表,若Entry元素的hash值与hashCode的hash值相等并且key与查找的key相等,则返回此Entry元素的value值
        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;
    }

举报

相关推荐

0 条评论