HashMap源码分析

这篇具有很好参考价值的文章主要介绍了HashMap源码分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

HashMap是Java集合框架中常用的一种数据结构,它是一种基于哈希表实现的映射表.在JDK1.8版本中,HashMap的get方法和put方法的实现与之前版本有些不同,下面我们来逐步分析其源码实现.

基本结构

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    // ... 
    
    /**
     * 默认初始容量为16
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 默认负载因子为0.75
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 最大容量:1 << 30(2的30次方)
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 存放元素的数组,长度总是2的幂次方
     */
    transient HashMap.Node<K,V>[] table;

    /**
     * 存放键值对的数量
     */
    transient int size;

    /**
     * 扩容操作的阈值
     */
    int threshold;

    /**
     * 负载因子,用于计算阈值
     */
    final float loadFactor;
 
	// ...   
}

get方法

    /**
     * 根据key获取value,如果key不存在则返回null
     *
     * @param key
     * @return
     */
    public V get(Object key) {
        // 获取key对应的Node节点
        HashMap.Node<K, V> e;
        // 调用getNode方法查找key对应的Node节点,并将查找结果赋值给e
        // 如果e为null就返回null否则返回e节点的value
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    /**
     * 根据key的哈希值和key查找对应的Node节点
     *
     * @param hash
     * @param key
     * @return
     */
    final HashMap.Node<K, V> getNode(int hash, Object key) {
        // 定义局部变量tab,first,e,n和k
        HashMap.Node<K, V>[] tab;
        HashMap.Node<K, V> first, e;
        int n;
        K k;
        // 如果table数据不为null且长度大于0,且第一个Node节点不为空,则开始查找Node节点
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
            // 如果第一个Node节点的哈希值与传入的hash值相等,且第一个Node节点的key和传入的key相等,则直接返回第一个Node节点
            if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 如果第一个Node节点不是要查找的Node节点,则开始遍历链表查找对应的Node节点
            if ((e = first.next) != null) {
                if (first instanceof HashMap.TreeNode)
                    // 如果第一个Node节点是红黑树节点,则调用红黑树节点的getTreeNode方法查找对应的Node节点
                    return ((HashMap.TreeNode<K, V>) first).getTreeNode(hash, key);
                // 如果第一个Node节点不是红黑树节点,则遍历链表查找对应的Node节点
                do {
                    // 如果遍历到的Node节点的hash值与传入的hash值相等,且Node节点的key和传入的key相等,则返回对应的Node节点
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        // 如果在table数组中没有找到对应的Node节点,则返回null
        return null;
    }

get方法工作流程如下:

  1. 根据key的hashCode计算出在哈希表中的位置
  2. 遍历该位置上的链表或树,查找对应的键值对
  3. 如果找到了对应的键值对,则返回对应的value;否则返回null

put方法

    /**
     * 向HashMap中添加一个key-value键值对
     *
     * @param key
     * @param value
     * @return
     */
    public V put(K key, V value) {
        // 根据key的哈希值和key查找对应的Node节点,并添加到HashMap中
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * 根据key的hash值和key添加一个键值对到HashMap中
     *
     * @param hash
     * @param key
     * @param value
     * @param onlyIfAbsent
     * @param evict
     * @return
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // 定义局部变量tab,p,n和i
        HashMap.Node<K, V>[] tab;
        HashMap.Node<K, V> p;
        int n, i;
        // 如果table数组为null或者长度为0,则先调用resize()方法初始化table数组
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 根据计算出来插入位置i插入新的键值对
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 如果插入的位置为null,则直接插入新的键值对
            tab[i] = newNode(hash, key, value, null);
        else {
            HashMap.Node<K, V> e;
            K k;
            // 如果插入的位置不为null,就遍历链表或树查找插入位置
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof HashMap.TreeNode)
                // 如果插入位置为红黑树节点,则调用putTreeVal方法插入新的键值对
                e = ((HashMap.TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
            else {
                // 遍历链表,查找插入位置
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        // 直接在链表末尾插入新的键值对
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 如果此时链表长度大于等于8,则将链表转化为红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 如果找到相同key,终止循环
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                // 如果存在相同key,则替换对应value
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            // 如果插入后的HashMap的大小大于阈值,则调用resize方法扩容HashMap
            resize();
        afterNodeInsertion(evict);
        return null;
    }

put方法工作流程如下:

  1. 根据key的hashCode值计算出在哈希表中的位置
  2. 如果该位置为空,则直接插入新的键值对
  3. 如果该位置不为空,则遍历该位置上的链表或树,查找是否已经存在对应的键值对
  4. 如果找到对应的键值对,则替换对应的value
  5. 如果没有找到对应的键值对,则将新的键值对插入到链表末尾
  6. 如果链表长度达到阈值(默认为8),则将链表转化为树
  7. 如果插入后HashMap的大小超过了阈值(默认容量的0.75),则扩容HashMap
  8. 插入完成后,执行一些必要的后续操作,例如更新修改次数等等

总的来说,HashMap的get方法和put方法都是基于哈希算法来实现键值对的查找和插入的,其中put方法需要考虑更多的情况,包括链表转换为树,扩容等等.

HashMap的容量为什么总是2的n次幂?

在Java中,HashMap的容量总是2的n次幂的原因是为了提高HashMap的性能.

HashMap内部使用一个数组来存储键值对,当添加一个键值对时,HashMap会根据建的hashCode值计算出它在数组中的索引位置.如果数组长度不是2的n次幂,那么计算索引时就需要进行取模操作,这会影响HashMap的性能.

如果数组长度时2的n次幂,那么计算索引时可以使用位运算(&操作),这比取模操作更快.而且,HashMap的扩容操作也要求长度时2的n次幂,这样在扩容时可以简化计算,提高性能.

另外,长度为2的n次幂的数组大小还有一个优点是,它可以保证数组的不同位置发生哈希冲突的概率比较平均,这可以减少哈希冲突的发生,提高HashMap的效率.文章来源地址https://www.toymoban.com/news/detail-406103.html

到了这里,关于HashMap源码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [JDK8环境下的HashMap类应用及源码分析] 看源码了解HashMap的扩容机制

    🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄、CSDN博客专家 🌹简历模板、学习资料、面试题库、技术互助 🌹文末获取联系方式 📝 [Java基础] StringBuffer 和 StringBuilder 类应用及源码分析 [Java基础] 数组应用及源码分析 [Java基础] String,分析

    2024年02月10日
    浏览(50)
  • HashMap底层源码解析及红黑树分析

    HashMap线程不安全,底层数组+链表+红黑树 面试重点是put方法,扩容 HashMap的put方法,首先通过key去生成一个hash值,第一次进来是null,此时初始化大小为16,i = (n - 1) hash计算下标值,第一次获取是null,直接放入一个Node节点,如果不是null,分成下面三种情况 1)如果发现hash和

    2024年02月02日
    浏览(51)
  • 一篇文章,轻松拿捏大厂必问的HashMap源码分析

    目录 一,JDK8之后HashMap的新特性 二,hashMap源码属性解读 (一),默认初始化容量数量:16 (二),最大数组容量:2^30 (三),默认负载因子:0.75f (四),触发树化条件1,链表阈值: (五),解树化的阈值:  (六),触发树化条件二,hash桶阈值(数组元素个数): 三

    2023年04月08日
    浏览(82)
  • [JDK8下的HashMap类应用及源码分析] 数据结构、哈希碰撞、链表变红黑树

    [Java基础] StringBuffer 和 StringBuilder 类应用及源码分析 [Java基础] 数组应用及源码分析 [Java基础] String,分析内存地址,源码 [JDK8环境下的HashMap类应用及源码分析] 第一篇 空构造函数初始化 [JDK8环境下的HashMap类应用及源码分析] 第二篇 看源码了解HashMap的扩容机制 [JDK8环境下的

    2024年02月10日
    浏览(44)
  • JavaSE进阶 | Map集合、HashMap集合、TreeMap集合

    目录 🏀Map集合概述  🥅Map接口常用的方法 🥅哈希表(散列表)数据结构 🥅同时重写HashCode和equals 🥅HashMap和Hashtable的区别 🥅Properties类 🥅TreeSet(TreeMap)集合 🥅自平衡二叉树数据结构 🥅实现比较器接口 🥅集合工具类Collections (1) Map和Collection没有继承关系,是一个平级的

    2023年04月09日
    浏览(43)
  • 如何遍历HashMap集合?

    在Java中,HashMap是一种常用的数据结构,它提供了快速的查找、插入和删除操作。当我们需要遍历HashMap中的所有元素时,可以利用三种不同的方法实现。 HashMap中存储的是键值对的形式,因此最简单的方法就是直接遍历键值对。我们可以通过以下代码实现: 上述代码中,我们

    2023年04月23日
    浏览(42)
  • Java集合之一——HashMap(辨析)

    看到一篇讲hashmap的文章,讲的很不错,但是有一点我觉得作者没有讲清楚,这里我说一下自己的理解。 原文,先看原文: https://blog.csdn.net/woshimaxiao1/article/details/83661464 前文概述,该博客的主要内容如下: 1. 什么是哈希表(主干为数组)、什么是哈希冲突、如何解决哈希冲突

    2024年02月15日
    浏览(44)
  • Map集合体系(HashMap,LinkedHashMap,TreeMap)

    目录 1.Map集合 2.hashMap集合 3.LinkedHashMap集合 4. TreeMap集合 1.Map集合         Map集合是键值对集合         格式:{key1=value1, key2=value2, key3=value3, ...}         Map系列集合的特点都是由键决定的,值只是一个附属品,值不做要求 2.实现类有哪些?,各自有什么特点?  

    2024年02月21日
    浏览(40)
  • HashMap集合万字源码详解(面试常考)

    散列,又称哈希(Hash),是一种数据处理方式。它通过特定的算法,将输入(比如字符串、文件等)转换成固定长度的一串(通常是数字),并且这个过程是不可逆的。这个过程中的算法就称为哈希函数,得到的结果就称为哈希值。 散列的主要作用是为了检索数据。通过散

    2024年02月02日
    浏览(42)
  • 从源码分析常见集合的区别之List接口

    说到Java集合,共有两大类分别是Collection和Map。今天就详细聊聊大家耳熟能详的List吧。 List接口实现自 Collection 接口,是Java的集合框架中的一员,List接口下又有 ArrayList 、 LinkedList 和线程安全的 Vector ,今天就简单分析一下 ArrayList 和 LinkedList 的异同以及各自的优势。 ArrayLi

    2024年02月12日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包