Java集合之HashMap/hashTable详解

 更新时间:2023年09月21日 08:50:32   作者:X-TIE  
这篇文章主要介绍了Java集合之HashMap/hashTable详解,Map是映射键值的对象,map不能包含重复键:每个键最多只能映射一个值,它模拟了数学函数的抽象,需要的朋友可以参考下

HashMap/hashTable详解

Map是映射键值的对象。map不能包含重复键:每个键最多只能映射一个值。它模拟了数学函数的抽象。

Map接口包括基本操作的方法(如put、get、remove、containsKey、containsValue、size和empty)、批量操作(如putAll和clear)和集合视图(如keySet、entrySet和values)。Java平台包含三个通用的映射实现:HashMap、TreeMap和LinkedHashMap。

它们的行为和性能与Set接口部分中描述的HashSet、TreeSet和LinkedHashSet类似。

下面从HashMap和hashTable两个容器分别介绍对比介绍一下。

下面我来分别看一下HashMap 和 hashTable 在无参构造函数实例化的具体实例:

由上图我们可以看到HashMap的无参数构造函数new 了一个:容量为16,加载因子为0.75,阈值为12的容器。

而hashTable的无参数构造函数则new 了一个:容量为11,加载因子为0.75,阈值为8的容器。

其中阈值为容量和加载因子的乘积,意思是如果容器到了这个值,那么就要实施扩容的机制了。下面我们看一下这两个容器到了阈值分别是如何扩容的呢?

首先是HashMap的扩容机制:

从源码上看,容器扩大了原容器的length*2倍。必须是2的冥。(2的几次方)

里面还有一个判断如果原来容器的容量已经达到了最大值,那么就把阈值调整到最大值,然后把原数组数据映射到新的更大的数组当中。这也就是说当数据量过多并且知道最大值的时候为了避免哈希表被重新散列(防止内部数据结构频繁被重新构建)。

然后我们看一下hashTable是如何扩容的:

原容器的大小乘以2+1,保证得到的数据是一个奇数。那么到这了我们考虑一下为什么table扩容要求是奇数,而map扩容必须是2的冥呢?

那么我们下面说一下HashMap的扩容机制以及确认元素位置的源码,来分析一下为什么设计成2的冥:

通过位运算符保证初始容量一定是2的冥

为了防止hash碰撞,在Entry数组(单链表)中为了保证每一个位置只有一个元素,通过hash%table.length=bucketIndex,bucketIndex为元素具体的位置,这样能够均匀的分布到容器的各个位置且不会有重复的。对集合操作效率也高。那么我们看一下源码中是如何找到元素具体的位置的:

为了减少碰撞HashMap是做了二次hash运算的。

h为最后计算的hash值,length为容器的容量。假设容量 = 16,我们计算一个hash值,来看一下h具体值为,并且我们计算一下indexFor的值是什么:

可以看到具体的hash值和在容器中的一个位置信息。

然后我们看一下,巧合的是根据我们的计算h & (length - 1) == h % length两个等式正好相等。且

这个的位运算的效率更高,这个应该就是容量必须为2的幂的原因。保证了数据分散的均匀。

并且通过二次hash减少碰撞,那么什么是碰撞呢?碰撞就是两个数计算出来的hash值一样,且equals e1.equals(e2)不相等,这样在一个hash位置上就会存储多个链表。在取值或者删除数据元素的时候效率比较低。

上面就是说的HashMap的容量为什么是2的冥的原因,下面来介绍一下hashTable的初始容量为什么是11,以及扩容机制?

hashTable的key获取hash为直接返回的当前key的hashCode值例如:如果是一个String的lisi返回3322014。

通过拆分lisi为char数组元素,且每个值拿到ASCII值的十进制。31*hash + 当前码值。

直接计算当前hash & long int的最大值%当前容器的容量,获得具体在容器中的位置。

int newCapacity = (oldCapacity << 1) + 1;这个是hashTable的一个扩容计算规则:保证了扩容后容量始终为奇数。

那么hashTable的扩容容量始终保证为奇数呢?

首先我猜测跟他的确认地址是有关系的,在就是由于hashTable全程加了同步锁为线程安全的,为了性能更高的操作容器才会这么设置,这块如果有小伙伴能讲解的比较清楚也欢迎评论交流指导。

最后总结一下:哈希表的大小为素数时,简单的取模哈希的结果会更加均匀,所以单从这一点上看,HashTable的哈希表大小选择,似乎更高明些。但另一方面我们又知道,在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。所以从hash计算的效率上,又是HashMap更胜一筹。之所以不一样是因为HashMap用的位移运算确认具体位置,而hashTable是直接用的模。(事实就是HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改动。HashMap和HashTable在计算hash时都用到了一个叫hashSeed的变量。这是因为映射到同一个hash桶内的Entry对象,是以链表的形式存在的,而链表的查询效率比较低,所以HashMap/HashTable的效率对哈希冲突非常敏感,所以可以额外开启一个可选hash(hashSeed),从而减少哈希冲突。)

在结尾总结性的补充一下这个hashTable和HashMap的异同:

1.首先父类不同。hashTable的父类是Dictionary<K,V>,HashMap的父类是AbstractMap<K,V>.

2.HashMap是支持null键和null值的,而HashTable在遇到null时,会抛出NullPointerException异常。

3.初始化大小不同,扩容机制不同。

4.hashTable为线程安全的,方法级别的强制同步。HashMap非线程安全的。所以HashMap效率性能要高。

相同点:都实现了Map<K,V>接口。

到此这篇关于Java集合之HashMap/hashTable详解的文章就介绍到这了,更多相关HashMap/hashTable详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 微信公众号开发之设置自定义菜单实例代码【java版】

    微信公众号开发之设置自定义菜单实例代码【java版】

    这篇文章主要介绍了微信公众号开发之设置自定义菜单实例代码,本实例是为了实现在管理后台实现微信菜单的添加删除管理。需要的朋友可以参考下
    2018-06-06
  • SpringBoot集成免费的EdgeTTS实现文本转语音

    SpringBoot集成免费的EdgeTTS实现文本转语音

    在需要文本转语音(TTS)的应用场景中(如语音助手、语音通知、内容播报等),Java生态缺少类似Python生态的Edge TTS 客户端库,不过没关系,现在可以通过 UnifiedTTS 提供的 API 来调用免费的 EdgeTTS 能力,本文给大家介绍了SpringBoot集成免费的EdgeTTS实现文本转语音
    2025-10-10
  • 详解spring中的Aware接口功能

    详解spring中的Aware接口功能

    Spring的依赖注入的最大亮点是所有的Bean对Spring容器的存在是没有意识的,我们可以将Spring容器换成其他的容器,Spring容器中的Bean的耦合度因此也是极低的,本文重点给大家介绍spring中的Aware接口,感兴趣的朋友一起看看吧
    2022-02-02
  • SpringBoot使用@NotEmpty、@NotBlank、@NotNull注解进行参数校验

    SpringBoot使用@NotEmpty、@NotBlank、@NotNull注解进行参数校验

    我们经常需要对请求参数进行校验,本文主要介绍了SpringBoot使用@NotEmpty、@NotBlank、@NotNull注解进行参数校验,具有一定的参考价值,感兴趣的可以了解一下
    2024-08-08
  • SpringBoot中实现JSON转Word格式的示例详解

    SpringBoot中实现JSON转Word格式的示例详解

    这篇文章主要为大家详细介绍了如何使用SpringBoot实现JSON转Word格式,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-05-05
  • 详解Java中的数组与字符串相关知识

    详解Java中的数组与字符串相关知识

    这篇文章主要介绍了详解Java中的数组与字符串相关知识,包括操作字符串的一些基本方法列举,需要的朋友可以参考下
    2015-09-09
  • Mybatis中updateBatch实现批量更新

    Mybatis中updateBatch实现批量更新

    本文主要介绍了Mybatis中updateBatch实现批量更新,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • SpringBoot中@GetMapping注解的使用

    SpringBoot中@GetMapping注解的使用

    @GetMapping注解是Spring Boot中最常用的注解之一,它可以帮助开发者定义和处理HTTP GET请求,本文就来介绍一下SpringBoot中@GetMapping注解的使用,感兴趣的可以了解一下
    2023-10-10
  • SpringBoot 中的异步处理机制详解

    SpringBoot 中的异步处理机制详解

    本文介绍了异步处理的基础配置、线程池的自定义以及常见应用场景,在实际应用中,异步处理可以有效提升应用的性能,改善用户体验,但同时也需要我们合理管理线程池,确保系统资源的高效利用,感兴趣的朋友跟随小编一起看看吧
    2025-01-01
  • Java后端Tomcat实现WebSocket实例教程

    Java后端Tomcat实现WebSocket实例教程

    WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成握手。本文给大家介绍Java后端Tomcat实现WebSocket实例教程,感兴趣的朋友一起学习吧
    2016-05-05

最新评论