Hash冲突

目录

1.哈希冲突

2.解决hash冲突

3.HashMap中如何解决Hash冲突


1.哈希冲突

简单讲就是:key值不同的元素可能会映象到哈希表的同一地址上。

2.解决hash冲突

Hash冲突,也就是经过一个函数结果作为地址去存放当前key value键值对(这个是hashmap存值方式)。
解决hash冲突发方法有
1)开放定址法,m为表长度,增量di有三种取法,线性探测再散列,平方探测再散列。
2)链地址法,就是key值取模再运算,java的HashMap就是这么实现的,在put()方法里面。
3)重哈希法,在创建hashmap的时候一般默认初始化容量,创建的hash表是桶的数量,负载因子:map的size/初始化容量,当hash表中负载因子达到负载极限,hash表会自动成倍增加容量,并将原有的对象重新分配加入新的值,成为rehash,rehash非常影响性能,所以初始化容量要设置好,不能太过浪费空间,也不能过小造成rehash情况经常出现。
4)建立一个公共溢出区域,就是把冲突的都放在另一个地方,不在表里面。

3.HashMap中如何解决Hash冲突

先看HashMap的数据结构,HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它通过计算散列码来决定存储的位置,HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,出来的hash值一样,不同对象出来的hash值一样,出现所谓hash冲突,HashMap底层通过链表解决hash冲突的。
HashMap其实就是一个Entry数组(类似pair),Entry对象中包含了链和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。
在Java8之前,如果发生hash冲突往往是将该value直接链接到该位置的其他所有value的头部,即相互冲突的所有value形成一个链表,因此,最坏情况HashMap的查找时间复杂度退化到O(n),在Java8中做了改进,一个是改头插法为尾插法,还有一个是当一个位置冲突过多时(大于等于8),存储的value将形成一排序二叉树,排序的依据为key的hashCode,这样在最坏情况下,性能也只退化到O(logn)。
这样的改进意义重大,一是从O(n)提升到O(logn)的时间开销(最坏情况),二是如果恶意程序知道我们利用的Hash算法,在纯链表情况下,发送大量请求导致hash碰撞,不停访问这些key使HashMap忙于查找,最终瘫痪。

### Hash冲突原因 Hash冲突是指不同的键通过哈希函数计算后得到相同的存储位置。这种现象的根本原因是哈希空间有限而可能的键值范围无限大,因此不可避免会出现多个键映射到同一地址的情况[^1]。 --- ### 解决方案及其涉及的数据结构 #### 1. **链地址法** 链地址法是最常见的解决哈希冲突的方法之一。其核心思想是在每个桶中维护一个链表(或者更复杂的数据结构),用于存储所有映射到该桶的键值对。当发生冲突时,新元素会被追加到对应链表中。 在Java中,`HashMap` 和 `HashSet` 使用的就是这种方法。具体来说,在JDK 1.8之前仅使用单向链表;而在JDK 1.8之后,为了提高性能,当链表长度超过一定阈值时会自动转换为红黑树以优化查找效率[^2]。 数据结构:链表或红黑树 --- #### 2. **再哈希法** 再哈希法的核心在于定义一组独立的哈希函数 {h₁, h₂, ..., hk} 。如果某个键 k 的初始哈希值产生了冲突,则依次尝试其他哈希函数直到找到未被占用的位置为止。此方法的优点是可以有效减少聚集效应,但缺点是增加了额外的时间开销以及算法设计难度[^3]。 数据结构:数组或其他支持随机访问的基础容器 --- #### 3. **建立公共溢出区** 在这种方式下,所有的冲突项都被放置在一个单独设立的区域——称为“溢出区”。每当检测到碰撞时,就将当前记录移至这个特殊部分并更新指针指向它。尽管简单易懂,但由于需要频繁跳转定位目标节点,故实际应用较少见。 数据结构:主散列表 + 溢出表 --- #### 4. **开放定址法** 开放定址法规定一旦发现某单元已被占据,则按照某种探查序列继续寻找下一个可用空闲槽位。常用的探测策略包括线性探测、二次探测和双重散列等。需要注意的是,这类技术可能会引发一次性和二次性聚簇问题,从而降低整体表现水平。 数据结构:单一连续内存块组成的数组 --- ```java // Java 实现简单的链地址法处理哈希冲突 (基于 HashMap) class MyHashMap<K, V> { static class Node<K, V> { final K key; V value; Node<K, V> next; public Node(K key, V value) { this.key = key; this.value = value; } } private int capacity; // 容量大小 private float loadFactor; // 负载因子 private Node<K, V>[] table; @SuppressWarnings("unchecked") public MyHashMap(int initialCapacity, float loadFactor) { this.capacity = initialCapacity; this.loadFactor = loadFactor; this.table = new Node[capacity]; } public void put(K key, V value) { int index = Math.abs(key.hashCode()) % capacity; Node<K, V> node = table[index]; while (node != null) { if (node.key.equals(key)) { node.value = value; // 更新已有值 return; } node = node.next; } // 插入新的节点 Node<K, V> newNode = new Node<>(key, value); newNode.next = table[index]; // 头插法构建链表 table[index] = newNode; } public V get(K key) { int index = Math.abs(key.hashCode()) % capacity; Node<K, V> node = table[index]; while (node != null) { if (node.key.equals(key)) { return node.value; } node = node.next; } return null; // 键不存在 } } ``` --- ### 总结 综上所述,针对 hash 冲突可以采取多种措施加以应对,其中最为广泛使用的便是链地址法。每种方法都有各自适用场景及优劣之处,开发者应根据实际情况灵活选用合适的技术手段来解决问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值