关于HashMap的一些疑问答疑

为什么hashmap要重新计算key的hashcode值?

      1.HashMap在内部使用哈希表来存储键值对。为了将键映射到哈希表的索引位置上,HashMap需要先计算键的哈希码(hash code)。哈希码是一个整数,它代表了键对象的特定值,类似于键的指纹。哈希码的计算通常通过调用键对象的hashCode()方法来完成。

      2. HashMap为什么要重新计算Key的hashCode值呢?这是因为在哈希表中,键的哈希码决定了它在数组中的存储位置,而数组的索引是有限的,可能小于哈希码的范围。举个例子,如果哈希码是一个很大的整数,而哈希表的大小是100,那么对于哈希码的每个可能值并不都有一个对应的数组索引位置。因此,需要对哈希码进行适当的处理,将其映射到数组索引的范围内。

      3.这个处理过程就是通过对哈希码进行取模操作,将其转换为一个合法的数组索引。在HashMap内部,取模操作通常使用位运算的方式,比如(hashCode & (capacity - 1))。其中hashCode是原始的键的哈希码,capacity是哈希表的大小,capacity - 1是一个用于取模的位掩码,它保证了计算结果在0到capacity - 1的范围内。

        4.重新计算哈希码的过程确保了键能够均匀地分布在哈希表的各个索引位置上,从而提高了HashMap的性能和效率。如果不重新计算哈希码,而直接使用键对象的原始哈希码,可能会导致键值对在哈希表中分布不均匀,导致哈希冲突增多,影响HashMap的性能。

HashMap和方法底层实现

HashMap是Java集合框架中的一个重要类,它实现了一个基于哈希表的键值对存储结构。在Java中,HashMap是常用的用于存储和快速查找数据的数据结构之一。以下是对HashMap的底层源码实现进行详细分析:

  1. 哈希表的实现: HashMap的底层数据结构是一个数组,称为"table"。数组中的每个元素称为"桶"(bucket)。每个桶可以包含一个或多个链表或红黑树,这些链表或红黑树用于解决哈希冲突(不同的键可能映射到相同的哈希码)。在Java 8及之后的版本中,HashMap引入了红黑树来优化链表,提高性能。

  2. Node和TreeNode: HashMap使用两个内部类来表示键值对,分别是NodeTreeNodeNode用于链表,而TreeNode用于红黑树。

  3. put()方法: HashMap的put方法用于将键值对添加到HashMap中。当添加一个键值对时,首先计算键的哈希码。然后,通过哈希码与当前table长度取模的方式来确定存储位置。如果该位置已经有其他键值对存在,就需要解决哈希冲突。

  4. 解决哈希冲突: 当两个或多个不同的键映射到同一个位置时,产生了哈希冲突。在Java 8及之后的版本中,HashMap使用链表和红黑树来解决冲突。

    • 链表:如果发生哈希冲突,新的键值对会被添加到链表的末尾。
    • 红黑树:如果链表长度超过8,并且table的大小大于等于64,会将链表转换为红黑树。这样做是为了优化查找性能,因为红黑树的查找时间复杂度是O(log n)。
  5. resize()方法: 为了保持HashMap的性能,当HashMap中的元素数量达到容量的75%时,会触发resize操作。resize操作会将HashMap的容量扩大一倍,并重新计算所有键值对的哈希码和存储位置,以保证哈希表的装载因子(load factor)不超过0.75。

  6. get()方法: HashMap的get方法用于根据给定的键查找对应的值。当调用get方法时,首先计算键的哈希码,然后根据哈希码找到存储位置。如果该位置存在键值对,就需要检查键是否相等。对于链表或红黑树,需要遍历链表或红黑树进行查找。

  7. remove()方法: HashMap的remove方法用于删除指定键的键值对。当调用remove方法时,首先计算键的哈希码,然后根据哈希码找到存储位置。如果该位置存在键值对,就需要检查键是否相等,然后进行删除操作。

这只是HashMap底层源码的一部分,实际的源码实现可能更加复杂,还涉及到并发控制、迭代器等方面的处理。HashMap的实现是一个精心设计的数据结构,能够在大多数情况下提供高效的存储和查找操作。然而,需要注意的是,HashMap不是线程安全的,如果在多线程环境下使用,需要进行适当的同步处理或选择线程安全的实现类,如ConcurrentHashMap。

Node和TreeNode是怎么形成的呢?

在Java的HashMap中,NodeTreeNode是两个不同的内部类,用于存储键值对。它们在解决哈希冲突时发挥不同的作用:

  1. Node类: Node是HashMap的旧版本中用于解决哈希冲突的内部类。当发生哈希冲突时,新的键值对会被添加到一个链表的末尾。每个Node对象包含了键、值和一个指向下一个节点的引用。

在Java 8之前的版本中,HashMap使用链表来处理冲突,所有键值对都被存储在这些Node节点组成的链表中。这种方式简单,但当链表过长时,查找的性能会降低,导致HashMap性能下降。

  1. TreeNode类: 为了优化链表过长时的查找性能,从Java 8开始,HashMap引入了红黑树来替代过长的链表。当链表的长度超过8时,并且table的大小大于等于64时,HashMap会将链表转换为红黑树。

TreeNode是用于红黑树的内部类。红黑树是一种自平衡的二叉查找树,它能够保持较好的查找性能。当链表转换为红黑树后,查找的时间复杂度从O(n)降低到O(log n)。

  1. Node和TreeNode的转换: 在HashMap中,当添加新的键值对时,会根据链表长度和table大小的条件判断是否需要将链表转换为红黑树,或者在删除操作后判断是否需要将红黑树转换回链表。
  • 将链表转换为红黑树:当链表长度超过8,并且table的大小大于等于64时,会将链表转换为红黑树。这样可以提高查找性能。

  • 将红黑树转换回链表:当红黑树的节点数量小于6时,会将红黑树转换回链表。因为在节点较少的情况下,链表的查找性能更优于红黑树。

需要注意的是,从Java 9开始,Java引入了更加现代化的散列映射实现Node,称为TreeNode。在新的实现中,TreeNode将被称为Node,并且有一些性能改进。但基本的原理和上述的概念仍然适用。

put方法

put()方法是HashMap中的一个核心方法,用于将键值对添加到HashMap中。以下是put()方法的详细步骤:

  1. 首先,根据传入的键对象计算其哈希码(hash code),通过调用键对象的hashCode()方法来获得哈希码。

  2. 接下来,通过哈希码与当前table数组的长度(table.length)进行位运算,计算键在table中的索引位置。计算方法为:index = (hash & (table.length - 1))。这一步保证了哈希码的值在table的合法索引范围内。

  3. 在得到索引位置后,查找这个位置是否已经有元素存在,如果没有,则直接将键值对添加到该位置,并结束。

  4. 如果该位置已经存在一个或多个元素(可能是一个链表或红黑树),则需要进行如下操作:

    a. 首先,遍历链表或红黑树,检查当前键是否已经存在于其中。如果找到相同的键(根据equals()方法判断),则更新对应的值。

    b. 如果没有找到相同的键,将新的键值对添加到链表或红黑树的末尾。如果链表长度超过阈值8,并且table的长度大于等于64,则将链表转换为红黑树,以提高查找性能。

  5. 在添加新键值对后,需要检查是否需要进行resize操作。HashMap使用装载因子(load factor)来衡量数组容量的使用情况,默认装载因子为0.75。当HashMap中的键值对数量达到容量的75%时,会触发resize操作。resize操作将会创建一个新的、更大的table,然后重新计算所有键值对的索引位置,并将它们添加到新的table中。

  6. 最后,put()方法返回旧的值(如果存在),如果键不存在,则返回null。

需要注意的是,HashMap中的键对象要正确实现hashCode()equals()方法,以确保相等的键具有相同的哈希码和相等的值。否则,即使键内容相同,HashMap也可能会将其视为不同的键值对。另外,由于HashMap不是线程安全的,如果在多线程环境下使用,需要进行适当的同步处理或选择线程安全的实现类,如ConcurrentHashMap

示例1:基本的put()方法用法

import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        // 创建一个新的HashMap
        HashMap<String, Integer> hashMap = new HashMap<>();

        // 添加键值对到HashMap中
        hashMap.put("apple", 50);
        hashMap.put("banana", 30);
        hashMap.put("orange", 40);

        // 获取键的值
        System.out.println("Number of apples: " + hashMap.get("apple")); // 输出:Number of apples: 50

        // 修改已有键的值
        hashMap.put("apple", 60);
        System.out.println("Number of apples after update: " + hashMap.get("apple")); // 输出:Number of apples after update: 60

        // 添加新的键值对
        hashMap.put("grapes", 25);

        // 打印HashMap中的键值对
        System.out.println("HashMap: " + hashMap);
    }
}


输出:
Number of apples: 50
Number of apples after update: 60
HashMap: {orange=40, grapes=25, banana=30, apple=60}
示例2:使用put()方法处理哈希冲突
import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        HashMap<String, Integer> hashMap = new HashMap<>();

        // 添加键值对到HashMap中,模拟哈希冲突
        hashMap.put("apple", 50);
        hashMap.put("banana", 30);
        hashMap.put("orange", 40);
        hashMap.put("avocado", 55);

        // 查看哈希冲突后的情况
        System.out.println("HashMap after handling hash collision: " + hashMap);
    }
}



输出:
bash
HashMap after handling hash collision: {orange=40, avocado=55, 
示例3:resize操作

import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        HashMap<String, Integer> hashMap = new HashMap<>();

        // 添加大量键值对,触发resize操作
        for (int i = 0; i < 100; i++) {
            hashMap.put("key" + i, i);
        }

        // 查看resize后的情况
        System.out.println("HashMap after resize: " + hashMap);
    }
}


输出:
HashMap after resize: {key64=64, key0=0, key98=98, key3=3, key68=6

在这些示例中,我们可以看到put()方法的用法,以及哈希冲突处理和resize操作是如何在HashMap中进行的。put()方法是HashMap中一个关键的操作,帮助我们在数据结构中添加和更新键值对。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值