Java集合--Hash、Hash冲突

一、Hash

  散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。 这个映射函数称做散列函数,存放记录的数组称做散列表。

  • 实现Hash算法的关键:实现hash算法 、解决hash冲突

1.Hash函数

  首先来说hash函数,java中对象都已一个hashCode()方法,那为什么还需要hash函数呢?hashCode是在jdk中是有符号int类型,这个一个很大的范围,如果散列表的数组能覆盖所有int值的话,就不需要hash函数了,当然内存不允许我们维护这么大的散列表。这时我们需要hash函数将原始hashCode映射到一个很小的数组上去。意思就是将超大超长或不定长的整形数据转换为唯一(理想情况,对于不同对象hash值应该不相同)的定长的hash值,常见的做法是取模法,也是jdk中的实现方式。

  • HashMap的hashCode实现:
1 static final int hash(Object key) {
2     int h;
3     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
4 }
5
6 static int indexFor(int h, int length) {
7     return h & (length-1);
8 }

  第一个hash函数有人称之为“扰动函数”,第二个indexFor函数在jdk8中去掉了,函数内的代码合并到了putVal中,个人认为这两个函数合并起来是一个完整的hash函数。
  h & (length-1) 这段代码的作用其实就是取模,假设数组初始化长度为16,那么length-1的结果为15,对应二进制为00001111,如果我们有一个大小为20的key,对应二进制为00010100,与运算后结果为00000100,对应十进制为4。
  这里数组的长度必须为2的次幂。由于对key进行了取模运算,所以我们知道当length=16的时候,我们会舍弃调掉key高位的值,只保留了低4位。本来int是32位,只是用低4位冲突是不是太容易发生了?
  所以第一个“扰动函数”的作用出现了,这个函数将key本身高16和低16位做了异或运算。

  尽管实现了如此有效的散列算法,但只是将不同对象之间hash碰撞的概率降低了,还是不能完全保证不发生hash冲突,因此要继续使用hash表的优点就要解决hash冲突的问题。

二、解决Hash冲突

1.开放定址法(线性探测,二次探测,伪随机探测)

  用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的 地址则表明表中无待查的关键字,即查找失败。

  当哈希表越来越满时聚集越来越严重,这导致产生非常长的探测长度,后续的数据插入将会非常费时。通常数据超过三分之二满时性能下降严重,因此设计哈希表关键确保不会超过这个数据容量的一半,最多不超过三分之二。

  • 用开放定址法建立散列表时,建表前须将表中所有单元(更严格地说,是指单元中存储的关键字)置空。
  • 空单元的表示与具体的应用相关。

  按照形成探查序列的方法不同,可将开放定址法区分为线性探查法、线性补偿探测法、随机探测等。

(1)线性探查法(Linear Probing)

  • 该方法的基本思想是将散列表T[0..m-1]看成是一个循环向量,若初始探查的地址为d(即h(key)=d),则最长的探查序列为:

d,d+l,d+2,…,m-1,0,1,…,d-1
  即:探查时从地址d开始,首先探查T[d],然后依次探查T[d+1],…,直到T[m-1],此后又循环到T[0],T[1],…,直到探查到T[d-1]为止。

  • 探查过程终止于三种情况:

  ①若当前探查的单元为空,则表示查找成功(若是插入则将key写入其中);
  ②若当前探查的单元中含有key,则查找成功,但对于插入意味着失败;
  ③若探查到T[d-1]时仍未发现空单元,则无论是查找还是插入均意味着失败(此时表满,需要扩容)。

  • 利用开放地址法的一般形式,线性探查法的探查序列为:

hi=(h(key)+i)%m 0≤i≤m-1 //i=1

  • 用线性探测法处理冲突,思路清晰,算法简单,但存在下列缺点:

  ①哈希表容量不能完全利用,并且扩容将会是灾难的,需要删除以前标记过的元素并需要从新计算所有元素的位置,在频繁的删除和插入时效率变得很低。
  ②按上述算法建立起来的哈希表,删除工作非常困难。假如要从哈希表 HT 中删除一个记录,按理应将这个记录所在位置置为空,但我们不能这样做,而只能标上已被删除的标记,否则,将会影响以后的查找。
  ③线性探测法很容易产生堆聚现象。所谓堆聚现象,就是存入哈希表的记录在表中连成一片。按照线性探测法处理冲突,如果生成哈希地址的连续序列愈长(即不同关键字值的哈希地址相邻在一起愈长),则当新的记录加入该表时,与这个序列发生冲突的可能性愈大。因此,哈希地址的较长连续序列比较短连续序列生长得快,这就意味着,一旦出现堆聚(伴随着冲突),就将引起进一步的堆聚。

(2)线性补偿探测法

  线性补偿探测法的基本思想是将线性探测的步长从 1 改为 Q ,即将上述算法中的 hi=(h(key)+i)%m改为:hi=(h(key)+Q)%m,这个Q是根据一定的增长率变化的(1、4、9...),这样使得数据分布的足够散乱,不容易出现聚堆现象,而且要求 Q 的变化能使全表得到完整的扫描,以便能探测到哈希表中的所有单元(当然还有其他的线性在散列算法规则,这里只讨论该种方式的再散列)。

(3)随机探测

  随机探测的基本思想是将线性探测的步长从常数改为随机数,即令:hi=(h(key)+RN)%m ,其中 RN 是一个随机数。在实际程序中应预先用随机数发生器产生一个随机序列,将此序列作为依次探测的步长。这样就能使不同的关键字具有不同的探测次序,从而可以避免或减少堆聚。基于与线性探测法相同的理由,在线性补偿探测法和随机探测法中,删除一个记录后也要打上删除标记。

2.链地址法(拉链法)

  拉链法解决冲突的做法是将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于 1,但一般均取α≤1。

  • 与开放定址法相比拉链法的优点

  ①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
  ②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
  ③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
  ④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

  • 拉链法的缺点

  当发生hash冲突时,需要生成链表由此需要额外占用空间,并且需要花一定的时间和精力维护冲突链表。在扩容的时候需要把所有元素进行重新hash并分配地址,算法较为复杂繁琐。

3.再哈希(了解)

  再hash法,就是算hashcode的方法不止一个,一个要是算出来重复啦,再用另一个算法去算。使用一定的算法逻辑的到一种在当前情况不会发生hash冲突的hash算法。

4.建立公共溢出区(了解)

  建立一个公共溢出区域,就是把冲突的都放在另一个地方,不在表里面。具体实现不做探讨了(不常用)。

原文地址:https://www.cnblogs.com/tag6254/p/9416946.html

时间: 2024-10-03 03:39:59

Java集合--Hash、Hash冲突的相关文章

Java 散列表 hash table

Java 散列表 hash table @author ixenos hash table, HashTable, HashMap, HashSet hash table 是一种数据结构 hash table 为每个对象计算一个整数,该整数被称为散列码 hash code hash code 是由对象的实例域产生的一个整数,具有不同的数据域的对象将产生不同的hash code 如果自定义类,就要负责实现这个类的hashCode方法,注意要与equals方法兼容,即如果a.equals(b)为tr

HashMap之Hash碰撞冲突解决方案及未来改进

说明:参考网上的两篇文章做了简单的总结,以备后查(http://blogread.cn/it/article/7191?f=wb  ,http://it.deepinmind.com/%E6%80%A7%E8%83%BD/2014/04/24/hashmap-performance-in-java-8.html) 1.HashMap位置决定与存储 通过前面的源码分析可知,HashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置.当程序执行put(String,Obect)方法 时

Java 学习之 Hash

Hash Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值.简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数. HASH函数(计算机算法领域) 基本概念 * 若结构中存在和关键字K相等的记录,则必定在f(K)的存储位置上.由

java之Secure hash functions

java之Secure hash functions A secure hash function will generate a large number, called the hash value, when given a document of some sort. This document can be of almost any type. We will be using simple strings in our examples. The function is a one

拉链法解决Hash节点冲突问题

<?php /* * hash::拉链法解决hash节点存储冲突问题 * ::2014-07-02 * ::Small_Kind */ class small_hash { private $size = 20;//hash节点大小 private $zone = null;//hash空间 //实例化函数,并设置一个初始hash节点大小,如果节点大小为null,则为默认节点大小 final public function __construct($size = null){ if(!is_nu

PHP核心技术与最佳实践之Hash表冲突

PHP核心技术与最佳实践之Hash表冲突 接着上一篇文章,测试后输出value1value2.当 $ht->insert('key12','value12'); Echo $ht ->find('key12');时, 发现输出value12value12.这是什么原因呢? 这个问题称为Hash表的冲突.由于insert的是字符串,采用的算法是将字符串的ASIIC码相加,按照此方法,冲突产生了.通过打印key12和key1的Hash值,发现他们都为8,也就说,value1和value12同时被存

【Java集合源码剖析】HashMap源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/36034955 HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap. HashMap 实现了Serializable接口,因此它支持序列化,

【JAVA集合】HashMap源码分析(转载)

原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储的对象是一个键值对对象(Entry<K,V>): HashMap补充说明 基于数组和链表实现,内部维护着一个数组table,该数组保存着每个链表的表头结点:查找时,先通过hash函数计算hash值,再根据hash值计算数组索引,然后根据索引找到链表表头结点,然后遍历查找该链表: HashMap数据

第八章.Java集合

Java集合类是一种特别有用的工具类,可用于存储数量不等的对象.Java集合大致可分为Set.List.Queue和Map四种体系 Set代表无序.不可重复的集合 List代表有序.重复的集合 Map代表具有映射关系的集合 Java5又增加了Queue代表一种队列集合 java集合概述: 为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),java提供了集合类. 集合类主要负责保存.盛装其他数据,因此,集合类也被称为容器类.所有的集合类都在java.util包下,后来为了处

[Java集合] 彻底搞懂HashMap,HashTable,ConcurrentHashMap之关联.

注: 今天看到的一篇讲hashMap,hashTable,concurrentHashMap很透彻的一篇文章, 感谢原作者的分享. 原文地址: http://blog.csdn.net/zhangerqing/article/details/8193118 Java集合类是个非常重要的知识点,HashMap.HashTable.ConcurrentHashMap等算是集合类中的重点,可谓"重中之重",首先来看个问题,如面试官问你:HashMap和HashTable有什么区别,一个比较简