`
lc52520
  • 浏览: 369118 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

HashMap实现及冲突【Z】

    博客分类:
  • java
阅读更多

了解 HashMap 原理对于日后的缓存机制多少有些认识。在网络中也有很多方面的帖子,但是很多都是轻描淡写,很少有把握的比较准确的信息,在这里试着不妨说解一二。

    对于 HashMap 主要以键值 (key-value) 的方式来体现,笼统的说就是采用 key 值的哈希算法来,外加取余最终获取索引,而这个索引可以认定是一种地址,既而把相应的 value 存储在地址指向内容中。这样说或许比较概念化,也可能复述不够清楚,来看列式更加清晰:

         int   hash=key.hashCode();//------------------------1

         int   index=hash%table.lenth;//table 表示当前对象的长度 -----------------------2

其实最终就是这两个式子决定了值得存储位置。但是以上两个表达式还有欠缺。为什么这么说?例如在 key.hashCode() 后可能存在是一个负整数,你会问:是啊,那这个时候怎么办呢?所以在这里就需要进一步加强改造式子 2 了,修改后的:

      int   index= hash&Ox7FFFFFFF)%table.lenth;

到这里又迷惑了,为什么上面是这样的呢?对于先前我们谈到在 hash 有可能产生负数的情况,这里我们使用当前的 hash 做一个“与”操作,在这里需要和 int 最大的值相“与”。这样的话就可以保证数据的统一性,把有符号的数值给“与”掉。而一般这里我们把二进制的数值转换成 16 进制的就变成了: Ox7FFFFFFF 。(注:与操作的方式为,不同为 0 ,相同为 1 )。而对于 hashCode() 的方法一般有:

     public int hashCode(){

                 int hash=0,offset,len=count;

                char[]  var=value;

               for(int i=0;i<len;i++){

                      h=31*hash+var[offset++];

                }

               return hash;

         }

说道这里大家可能会说,到这里算完事了吧。但是你可曾想到如果数据都采用上面的方式,最终得到的可能 index 会相同怎么办呢?如果你想到的话,那恭喜你 ! 又增进一步了,这里就是要说到一个名词:冲突率。是的就是前面说道的一旦 index 有相同怎么办?数据又该如何存放呢,而且这个在数据量非常庞大的时候这个基率更大。这里按照算法需要明确的一点:每个键( key )被散列分布到任何一个数组索引的可能性相同,而且不取决于其它键分布的位置。这句话怎么理解呢?从概率论的角度,也就是说如果 key 的个数达到一个极限,每个 key 分布的机率都是均等的。更进一步就是:即便 key1 不等于 key2 也还是可能 key1.hashCode()=key2.hashCode()

对于早期的解决冲突的方法有折叠法( folding) ,例如我们在做系统的时候有时候会采用部门编号附加到某个单据标号后,这里比如产生一个 9 11 位的编码。通过对半折叠做。

现在的策略有:

1.        键式散列  

2.        开放地址法

 

 

在了解这两个策略前,我们先定义清楚几个名词解释:

threshold[ 阀值 ] ,对象大小的边界值 ;

loadFactor[ 加载因子 ]=n/m ;其中 n 代表对象元素个数, m 表示当前表的容积最大值

threshold=(int)table.length*loadFactor

清晰了这几个定义,我们再来看具体的解决方式

 

 

键式散列:

我们直接看一个实例,这样就更加清晰它的工作方式,从而避免文字定义。我们这里还是来举一个图书编号的例子,下面比如有这样一些编号:

                         78938 -000 0

                          45678-0001

                         72678-0002

                         24678-0001

                          16678-0001

                         98678-0003

                         85678-0002

                         45232-0004

步骤:

1.        把编号作为 key, 即: int hash=key.hashCode();

2.        int index=hash% 表大小;

3.        逐步按顺序插入对象中

现在问题出现了:对于编号通过散列算法后很可能产生相同的索引值,意味着存在冲突。

链

解释上面的操作:如果对于 key.hashCode() 产生了冲突(比如途中对于插入 24678-0001 对于通过哈希算法后可能产生的 index 或许也是 501 ),既而把相应的前驱有相同的 index 的对象指向当前引用。这也就是大家认定的单链表方式。以此类推

而这里存在冲突对象的元素放在 Entry 对象中, Entry 具有以下一些属性:

int hash;

Object key;

Entry next;

对于 Entry 对象就可以直接追溯到链表数据结构体中查阅。

 

 

开放地址法:

1.          线性地址探测法:

如何理解这个概念呢,一句话:就是通过算法规则在对象地址 N+1 中查阅找到为 NULL 的索引内容。

处理方式:如果 index 索引与当前的 index 有冲突,即把当前的索引 index+1 。如果在 index+1 已经存在占位现象( index+1 的内容不为 NULL )试图接着 index+2 执行。。。直到找到索引为内容为 NULL 的为止。这种处理方式也叫:线性地址探测法 (offset-of-1)

 

如果采用线性地址探测法会带来一个效率的不良影响。现在我们来分析这种方式会带来哪些不良因素。大家试想下如果一个非常庞大的数据存储在 Map 中, 假如在某些记录集中有一些数据非常相似(他们产生的索引在内存的某个块中非常的密集),也就是说他们产生的索引地址是一个连续数值,而造成数据成块现象。 另一个致命的问题就是在数据删除后,如果再次查询可能无法定到下一个连续数字,这个又是一个什么概念呢?例如以下图片就很好的说明开发地址散列如何把数据 按照算法插入到对象中:

开

对于上图的注释步骤说明:

从数据“ 78938-0000 ”开始通过哈希算法按顺序依次插入到对象中,例如 78938-0000 通过换

算得到索引为 0 ,当前所指内容为 NULL 所以直接插入; 45678-0001 同样通过换算得到索引为地址 501 所指内容,当前内容为 NULL 所以也可以插入; 72678-0002 得到索引 502 所指内容,当前内容为 NULL 也可以插入;请注意当 24678-0001 得到索引也为 501 ,当前地址所指内容为 45678-0001 。即表示当前数据存在冲突,则直接对地址 501+1=502 所指向内容为 72678-0002 不为 NULL 也不允许插入,再次对索引 502+1=503 所指内容为 NULL 允许插入。。。依次类推只要对于索引存在冲突现象,则逐次下移位知道索引地址所指为 NULL ;如果索引不冲突则还是按照算法放入内容。对于这样的对象是一种插入方式,接下来就是我们的删除 (remove) 方法了。按照常理对于删除,方式基本区别不大。但是现在问题又出现了,如果删除的某个数据是一个存在冲突索引的内容,带来后续的问题又会接踵而来。那是什么问题呢?我们还是同样来看看图示的描述,对于图 -2 中如果删除 (remove) 数据 24678-0001 的方法如下图所示:

删

对于我们会想当然的觉得只要把指向数据置为 NULL 就可以 , 这样的做法对于删除来说当然是没有问题的。如果再次定位检索数据 16678-0001 不会成功,因为这个时候以前的链路已经堵上了,但是需要检索的数据事实上又存在。那我们如何来解决这个问题呢?对于 JDK 中的 Entry 类中的方法存在一个: boolean markedForRemoval; 它就是一个典型的删除标志位,对于对象中如果需要删除时,我们只是对于它做一个“软删除”即置一个标志位为 true 就可以。而插入时,默认状态为 false 就可以。这样的话就变成以下图所示:

 

 

标

通过以上方式更好的解决冲突地址删除数据无法检索其他链路数据问题了。

 

2.          双散列(余商法)

在 了解开放地址散列的时候我们一直在说解决方法,但是大家都知道一个数据结构的完善更多的是需要高效的算法。这当中我们却没有涉及到。接下来我们就来看看在 开放地址散列中它存在的一些不足以及如何改善这样的方法,既而达到无论是在方法的解决上还是在算法的复杂度上更加达到高效的方案。

在图 2-1 中类似这样一些数据插入进对象,存在冲突采用不断移位加一的方式,直到找到不为 NULL 内 容的索引地址。也正是由于这样一种可能加大了时间上的变慢。大家是否注意到像图这样一些数据目前呈现出一种连续索引的插入,而且是一种成块是的数据。如果 数据量非常的庞大,或许这种可能性更大。尽管它解决了冲突,但是对于数据检索的时间度来说,我们是不敢想象的。所有分布到同一个索引 index 上的 key 保持相同的路径: index,index+1,index+2… 依此类推。更加糟糕的是索引键值的检索需要从索引开始查找。正是这样的原因,对于线性探索法我们需要更进一步的改进。而刚才所描述这种成块出现的数据也就定义成:簇。而这样一种现象称之为:主簇现象。

(主簇:就是冲突处理允许簇加速增长时出现的现象)而开放式地址冲突也是允许主簇现象产生的。那我们如何来避免这种主簇现象呢?这个方式就是我们要来说明的:双散列解决冲突法了。主要的方式为:

u        int hash=key.hasCode();

u        int index=(hash&Ox7FFFFFFF)%table.length;

u        按照以上方式得到索引存在冲突,则开始对当前索引移位,而移位方式为:

       offset=(hash&Ox7FFFFFFF)/table.length;

u        如果第一次移位还存在同样的冲突,则继续:当前冲突索引位置(索引号 + 余数) % .length

u        如果存在的余数恰好是表的倍数,则作偏移位置为一下移,依此类推

 

这样双散列冲突处理就避免了主簇现象。至于 HashSet 的原理基本和它是一致的,这里不再复述。在这里其实还是主要说了一些简单的解决方式,而且都是在一些具体参数满足条件下的说明,像一旦数据超过初始值该需要 rehash ,加载因子一旦大于 1.0 是何种情况等等。还有很多问题都可以值得我们更加进一步讨论的,比如:在 java.util.HashMap 中的加载因子为什么会是 0.75

分享到:
评论

相关推荐

    哈希计算工具 java-hash.7z

    10. **Java中的冲突解决**: 在Java集合框架中,如HashMap,哈希冲突是通过链表或红黑树来解决的。当两个键的哈希值相同但键本身不同时,它们会被放入同一个桶中,形成链表或红黑树结构。 综上所述,`java-hash.7z` ...

    shootgame.7z

    例如,可能使用ArrayList或LinkedList存储游戏对象,用HashMap实现对象间的关联关系,或者使用优先队列来优化子弹和敌机的碰撞检测。此外,游戏中的AI算法,如敌机的随机运动策略,也可能涉及到概率计算和状态机的...

    如何正确实现Java中的HashCode共6页.pdf.z

    这个方法的主要目的是为对象生成一个整数哈希码,它通常用于优化数据结构,如哈希表(如`HashMap`和`HashSet`)。本教程“如何正确实现Java中的HashCode共6页.pdf”详细讲解了在Java中正确实现`hashCode()`的重要性...

    java源码:哈希计算工具 java-hash.7z

    这个"java-hash.7z"压缩包包含了一个Java实现的哈希计算工具,这是一份经典的学习资源,可以帮助开发者深入理解哈希算法及其在Java中的应用。 哈希(Hash)函数是一种将任意长度输入(也叫做预映射pre-image)通过...

    Java集合框架常见面试题.7z

    这些接口和类包括List、Set、Map等,以及它们的实现如ArrayList、HashSet、HashMap等。 2. **List、Set和Queue的区别是什么?** - **List**:有序的集合,允许重复元素,可以有索引,例如ArrayList和LinkedList。 ...

    Java思维导图.7z

    5. **集合框架**:Java集合框架包括List、Set、Queue和Map接口,以及ArrayList、LinkedList、HashSet、HashMap等实现类,它们是存储和操作对象的主要工具。 6. **输入/输出流**:Java的I/O流系统允许程序进行数据的...

    ==代替Object#equals() - 加速在容器类中搜索元素速度的可能性

    一个良好的`hashCode()`实现应尽可能地分散哈希值,减少哈希冲突,从而提高查找效率。 总结来说,优化`equals()`和`hashCode()`方法可以极大提升容器类中元素搜索的速度。具体优化策略取决于应用场景,包括对象类型...

    java 基础之(equals hashcode)

    这很重要,因为哈希表(如 `HashMap`)依赖于 `hashCode()` 来确定对象存储的位置,以实现快速查找。如果两个对象相等但 `hashCode()` 不同,那么哈希表可能无法正确地将它们关联起来,导致性能下降。 在重写 `...

    09.Dictionary.A.hashing.Call-by-value

    许多高级编程语言提供了词典或其类似物作为内置的数据结构(例如Python中的dict、Java中的HashMap或JavaScript中的Object)。词典允许程序员以键为索引来高效地存储、访问和删除数据。 知识点六:Tsinghua ...

    Java_A-Z

    - **ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等实现类**:各有优缺点,适用于不同场景。 5. **多线程** - **Thread类和Runnable接口**:创建线程的两种方式。 - **线程同步机制**:包括...

    hashcode代码

    反之,不相等的对象应该尽可能产生不同的哈希值,以减少哈希冲突的可能性,提高哈希表(如 HashMap 或 HashSet)的性能。 `equals()` 方法同样来自 Object 类,用于比较两个对象是否相等。默认情况下,`equals()` ...

    java中hashcode()和equals()的详解.docx

    - **传递性**:对于任何非空引用值`x`、`y`和`z`,如果`x.equals(y)`返回`true`且`y.equals(z)`返回`true`,那么`x.equals(z)`也应该返回`true`。 - **一致性**:如果`x.equals(y)`返回`true`,并且在这之后`x`和`...

    Java中的equals()和hashCode()契约Ja

    3. **传递性**:如果x.equals(y)返回`true`且y.equals(z)返回`true`,那么x.equals(z)也应返回`true`。 4. **一致性**:对于任何非空引用x和y,如果x和y的内容始终保持不变,则多次调用x.equals(y)应始终返回相同的...

    Java程序设计慕课版)自测试题5套及答案大学期末复习资料.docx

    10. `HashMap`重写`hashCode()`原则:为了保持哈希表的正确工作,重写`hashCode()`时,相同的对象应返回相同的哈希值,不同的对象应尽可能返回不同的哈希值,以减少冲突。 11. 编程题:在循环结束后尝试打印`i`,...

    Java equals 方法与hashcode 方法的深入解析.rar

    3. 传递性:如果x.equals(y)为true,且y.equals(z)为true,那么x.equals(z)也应为true。 4. 一致性:对于非null的引用x和y,如果多次调用x.equals(y),结果始终相同,除非x或y被修改。 5. 非null:对于非null的引用x...

    2021-2022计算机二级等级考试试题及答案No.11988.docx

    7. 表达式y+=z--/++x的计算过程中,先执行前缀++x,x变为2,然后执行z--,z变为2,所以表达式变为y+=2/2,y初始值为2,因此y最后的值是2+1=3,但题目答案是A,这可能是一个错误。 8. 对象的equals方法用于比较两个...

    一套完整的java面试题

    HashMap和Hashtable都是Map接口的实现,HashMap是非同步的,而Hashtable是同步的。 16. float f = 3.4是不正确的,应该写为float f = 3.4f。 17. final声明变量不可变,finally确保代码块总会执行,finalize是对象...

    Java高新技术3

    通过实现Runnable接口或继承Thread类,开发者可以创建并运行多个线程来实现并发执行。同时,Java提供了synchronized关键字和Lock接口来处理线程同步,防止数据竞争和死锁。 2. **JVM内存管理**:理解Java虚拟机...

    allequals:Java equalshashCode 基准代码

    - 传递性:如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也应返回true。 - 一致性:如果对象的equals比较不改变,多次调用x.equals(y)应该返回相同结果。 - null检查:对于任何非null引用x...

Global site tag (gtag.js) - Google Analytics