MAP的底层原理

本文介绍了Map数据结构,其元素为键值对,主要实现类有HashMap和TreeMap,开发常用HashMap。还阐述了数组、链表的特点,引出哈希表。详细讲解了HashMap底层原理,包括保存数据过程,以及当元素个数超过数组大小×loadFactor(默认0.75)时会进行扩容。

Map数据结构

Map也是容器的一种,那么我们以前看到的每一种容器,都有响应的数据结构,例如数组是一组连续的存储空间,链表是无序的,包含指针域和值域的容器。

Map的每一个元素叫做键值对,所谓键值对其实就是 “键” 和 “值” 组成的一对。

map的主要实现类是hashmap和treemap,在java开发过程中主要用到的是hashmap。下面简单介绍一下hashmap原理
数组

数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

链表

链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

哈希表

那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表((Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。

哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为“链表的数组”
  在这里插入图片描述

hashmap底层原理
HashMap保存数据的过程为:首先判断key是否为null,若为null,则直接调用putForNullKey方法。若不为空则先计算key的hash值,然后根据hash值搜索在table数组中的索引位置,如果table数组在该位置处有元素,则通过比较是否存在相同的key,若存在则覆盖原来key的value,否则将该元素保存在链头(最先保存的元素放在链尾)。若table在该处没有元素,则直接保存。

hashmap什么时候进行扩容呢?当hashmap中的元素个数超过数组大小×loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16×0.75=12的
时候,就把数组的大小扩展为2×16=32,即扩大一倍,然后重新计算每个元素在数组中的位置。

### 哈希 Map底层实现原理 #### 数据结构 哈希 Map 是基于 **哈希表**(Hash Table)实现的键值对存储结构。其核心思想是通过一个哈希函数将键(Key)映射到数组的一个索引位置,从而实现快速的查找、插入和删除操作。在 Java 中,HashMap 使用了 **数组 + 链表 + 红黑树** 的混合数据结构来优化性能[^2]。 - **数组**:用于存储主桶(Bucket),每个桶对应一个索引。 - **链表**:当发生哈希冲突时,使用链表来存储多个键值对。 - **红黑树**:当链表长度超过阈值(默认为 8)时,链表会转换为红黑树以提高查找效率[^1]。 #### 冲突解决方法 由于哈希函数可能会导致不同的键映射到相同的索引位置,因此需要有效的冲突解决机制。常见的冲突解决策略包括以下几种: 1. **链地址法(Separate Chaining)** - 每个桶维护一个链表或红黑树,所有映射到同一位置的键值对都会被存储在这个链表中。 - 在 Java 的 HashMap 中,当链表长度超过一定阈值时,链表会转换为红黑树以提升查询性能[^1]。 2. **开放地址法(Open Addressing)** - 当发生冲突时,在数组中寻找下一个空闲槽位进行存储。常见的探测方式包括线性探测、二次探测和双重哈希等。 - 这种方法不需要额外的数据结构,但在负载因子较高时容易产生聚集现象,影响性能[^4]。 3. **再哈希法(Rehashing)** - 引入第二个哈希函数重新计算冲突键的位置,减少冲突概率。 - 虽然可以缓解聚集问题,但增加了哈希计算的开销[^4]。 4. **公共溢出区(Overflow Area)** - 设置一个专门的区域用于存放冲突的键值对。 - 适用于冲突较少的情况,但在高冲突场景下会导致性能下降[^3]。 #### 负载因子与扩容机制 负载因子(Load Factor)是衡量哈希表填充程度的一个指标,通常定义为 **元素数量 / 数组容量**。当哈希表中的元素数量超过当前容量乘以负载因子时,哈希表会触发扩容操作。 - **Java HashMap 默认负载因子为 0.75**,这是一个在时间和空间上取得平衡的折中值[^1]。 - 扩容时,HashMap 会创建一个新的数组,并将原有数据重新哈希分配到新数组中,这一过程称为 **rehashing**。 示例代码片段展示了 HashMap 的基本插入逻辑: ```java public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); 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; return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } ``` #### 渐进式扩容 在一些语言如 Go 中,map实现采用了 **渐进式扩容**(Incremental Resizing)策略。即在扩容过程中不会一次性迁移所有数据,而是逐步将旧数据迁移到新数组中。这种方式可以避免在扩容时阻塞整个 map 的访问,提升并发性能。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值