ImageVerifierCode 换一换
格式:DOCX , 页数:19 ,大小:112.43KB ,
资源ID:3305584      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/3305584.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(HashMap设计原理和实现分析.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

HashMap设计原理和实现分析.docx

1、HashMap设计原理和实现分析HashMap在Java开发中有着非常重要的角色地位,每一个Java程序员都应该了解HashMap。 本文主要从源码角度来解析HashMap的设计思路,并且详细地阐述HashMap中的几个概念,并深入探讨HashMap的内部结构和实现细节,讨论HashMap的性能问题,并且在文中贯穿着一些关于HashMap常见问题的讨论。读完本文,你会了解到: 1. HashMap的设计思路和内部结构组成 2. HashMap中的一些概念:什么是阀值?为什么会有阀值?什么是加载因子?它们有什么作用? 3. HashMap的性能问题以及使用事项 4. HashMap的源码实现解析

2、 5. 为什么JDK建议我们重写Object.equals(Object obj)方法时,需要保证对象可以返回相同的hashcode值?1. HashMap设计思路以及内部结构组成HashMap设计思路 Map是一种以键值对存储数据的容器,而HashMap则是借助了键值Key的hashcode值来组织存储,使得可以非常快速和高效地地根据键值key进行数据的存取。对于键值对,HashMap内部会将其封装成一个对应的Entry对象,即Entry对象是键值对的组织形式; 对于每个对象而言,JVM都会为其生成一个hashcode值。HashMap在存储键值对Entry的时候,会根据Key的hashco

3、de值,以某种映射关系,决定应当将这对键值对Entry存储在HashMap中的什么位置上; 当通过Key值取数据的时候,然后根据Key值的hashcode,以及内部映射条件,直接定位到Key对应的Value值存放在什么位置,可以非常高效地将Value值取出。 为了实现上述的设计思路,在HashMap内部,采用了数组+链表的形式来组织键值对Entry。HashMap内部维护了一个Entry table数组,当我们使用new HashMap()创建一个HashMap时,Entry table的默认长度为16。Entry table的长度又被称为这个HashMap的容量(capacity);对于En

4、try table的每一个元素而言,或为null,或为由若干个Entry组成的链表。HashMap中Entry的数目被称为HashMap的大小(size);Entry table中的某一个元素及其对应的Entry又被称为桶(bucket); 其结构如下图所示: HashMap内部组织结构由上图所示,现在来看一下HashMap的基本工作流程:2. 什么是阀值?为什么会有阀值?什么是加载因子?它们有什么作用?HashMap设计的初衷,是为了尽可能地迅速根据Key的hashCode值, 直接就可以定位到对应的Entry对象,然后得到Value。 请读者考虑这样一个问题: 当我们使用HashMap m

5、ap = new HashMap()语句时,我们会创建一个HashMap对象,它内部的Entry table的大小为16,我们假定Entry table的大小会改变。现在,我们现在向它添加160对Key值完全不同的键值对,那么,该HashMap内部有可能下面这种情况:即对于每一个桶中的由Entry组成的链表的长度会非常地长!我们知道,对于查找链表操作的时间复杂度是很高的,为O(n)。这样的一个HashMap的性能会很低很低,如下图所示:现在再来分析一下这个问题,当前的HashMap能够实现: 1. 根据Key的hashCode,可以直接定位到存储这个Entry的桶所在的位置,这个时间的复杂度为

6、O(1); 2. 在桶中查找对应的Entry对象节点,需要遍历这个桶的Entry链表,时间复杂度为O(n); 那么,现在,我们应该尽可能地将第2个问题的时间复杂度o(n)降到最低,读者现在是不是有想法了:我们应该要求桶中的链表的长度越短越好!桶中链表的长度越短,所消耗的查找时间就越低,最好就是一个桶中就一个Entry对象节点就好了! 这样一来,桶中的Entry对象节点要求尽可能第少,这就要求,HashMap中的桶的数量要多了。 我们知道,HashMap的桶数目,即Entry table数组的长度,由于数组是内存中连续的存储单元,它的空间代价是很大的,但是它的随机存取的速度是Java集合中最快的

7、。我们增大桶的数量,而减少Entry链表的长度,来提高从HashMap中读取数据的速度。这是典型的拿空间换时间的策略。 但是我们不能刚开始就给HashMap分配过多的桶(即Entry table 数组起始不能太大),这是因为数组是连续的内存空间,它的创建代价很大,况且我们不能确定给HashMap分配这么大的空间,它实际到底能够用多少,为了解决这一个问题,HashMap采用了根据实际的情况,动态地分配桶的数量。HashMap的权衡策略 要动态分配桶的数量,这就要求要有一个权衡的策略了,HashMap的权衡策略是这样的:如果HashMap的大小HashMap的容量(即Entry table的大小)

8、*加载因子(经验值0.75)则HashMap中的Entry table 的容量扩充为当前的一倍; 然后重新将以前桶中的Entry链表重新分配到各个桶中上述的HashMap的容量(即Entry table的大小) * 加载因子(经验值0.75)就是所谓的阀值(threshold): 最后,请读者看一个实例: 默认创建的HashMap map =new HashMap();map的容量是16,那么,当我们往map中添加第几个完全不同的键值对时,HashMap的容量会扩充呢? 呵呵,很简单的计算:由于默认的加载因子是0.75,那么,此时map的阀值是16*0.75 = 12,即添加第13个键值对的时

9、候,map的容量会扩充一倍。 这时候读者可能会有疑问:本来Entry table的容量是16,当放入12个键值对后,不是至少还剩下4个Entry table元素没有被使用到吗?这不是浪费了宝贵的空间了吗?! 确实如此,但是为了尽可能第减少桶中的Entry链表的长度,以提高HashMap的存取性能,确定的这个经验值。如果读者你对存取效率要求的不是太高,想省点空间的话,你可以new HashMap(int initialCapacity, float loadFactor)构造方法将这个因子设置得大一些也无妨。2. HashMap的算法实现解析HashMap的算法实现最重要的两个是put() 和g

10、et() 两个方法,下面我将分析这两个方法:javaview plaincopy print?1. publicVput(Kkey,Vvalue);2. publicVget(Objectkey);另外,HashMap支持Key值为null 的情况,我也将详细地讨论这个问题。 1. 向HashMap中存储一对键值对流程-put()方法实现:put()方法-向HashMap存储键值对 a. 获取这个Key的hashcode值,根据此值确定应该将这一对键值对存放在哪一个桶中,即确定要存放桶的索引;b. 遍历所在桶中的Entry链表,查找其中是否已经有了以Key值为Key存储的Entry对象,c1.

11、 若已存在,定位到对应的Entry,其中的Value值更新为新的Value值;返回旧值;c2. 若不存在,则根据键值对 创建一个新的Entry对象,然后添加到这个桶的Entry链表的头部。d. 当前的HashMap的大小(即Entry节点的数目)是否超过了阀值,若超过了阀值(threshold),则增大HashMap的容量(即Entry table 的大小),并且重新组织内部各个Entry排列。详细流程如下列的代码所示:javaview plaincopy print?1. /*2. *将键值对存到HashMap中,如果Key在HashMap中已经存在,那么最终返回被替换掉的Value值。3.

12、 *Key和Value允许为空4. */5. publicVput(Kkey,Vvalue)6. 7. /1.如果key为null,那么将此value放置到table0,即第一个桶中8. if(key=null)9. returnputForNullKey(value);10. /2.重新计算hashcode值,11. inthash=hash(key.hashCode();12. /3.计算当前hashcode值应当被分配到哪一个桶中,获取桶的索引13. inti=indexFor(hash,table.length);14. /4.循环遍历该桶中的Entry列表15. for(Entrye

13、=tablei;e!=null;e=e.next)16. Objectk;17. /5.查找Entry链表中是否已经有了以Key值为Key存储的Entry对象,18. /已经存在,则将Value值覆盖到对应的Entry对象节点上19. if(e.hash=hash&(k=e.key)=key|key.equals(k)/请读者注意这个判定条件,非常重要!20. VoldValue=e.value;21. e.value=value;22. e.recordAccess(this);23. returnoldValue;24. 25. 26. modCount+;27. /6不存在,则根据键值对

14、创建一个新的Entry对象,然后添加到这个桶的Entry链表的头部。28. addEntry(hash,key,value,i);29. returnnull;30. 31. 32. /*33. *Key为null,则将Entry放置到第一桶table0中34. */35. privateVputForNullKey(Vvalue)36. for(Entrye=table0;e!=null;e=e.next)37. if(e.key=null)38. VoldValue=e.value;39. e.value=value;40. e.recordAccess(this);41. returno

15、ldValue;42. 43. 44. modCount+;45. addEntry(0,null,value,0);46. returnnull;47. javaview plaincopy print?1. /*2. *根据特定的hashcode重新计算hash值,3. *由于JVM生成的的hashcode的低字节(lowerbits)冲突概率大,(JDK只是这么一说,至于为什么我也不清楚)4. *为了提高性能,HashMap对Key的hashcode再加工,取Key的hashcode的高字节参与运算5. */6. staticinthash(inth)7. /Thisfunctionen

16、suresthathashCodesthatdifferonlyby8. /constantmultiplesateachbitpositionhaveabounded9. /numberofcollisions(approximately8atdefaultloadfactor).10. h=(h20)(h12);11. returnh(h7)(h4);12. 13. 14. /*15. *返回此hashcode应当分配到的桶的索引16. */17. staticintindexFor(inth,intlength)18. returnh&(length-1);19. 当HashMap的大小

17、大于阀值时,HashMap容量的扩充算法当当前的HashMap的大小大于阀值时,HashMap会对此HashMap的容量进行扩充,即对内部的Entry table 数组进行扩充。HashMap对容量(Entry table数组长度) 有两点要求:1. 容量的大小应当是 2的N次幂;2. 当容量大小超过阀值时,容量扩充为当前的一倍;这里第2点很重要,如果当前的HashMap的容量为16,需要扩充时,容量就要变成16*2 = 32,接着就是32*2=64、64*2=128、128*2=256.可以看出,容量扩充的大小是呈指数级的级别递增的。 这里容量扩充的操作可以分为以下几个步骤:1. 申请一个新

18、的、大小为当前容量两倍的数组; 2. 将旧数组的Entry table中的链表重新计算hash值,然后重新均匀地放置到新的扩充数组中; 3. 释放旧的数组;由上述的容量扩充的步骤来看,一次容量扩充的代价非常大,所以在容量扩充时,扩充的比例为当前的一倍,这样做是尽量减少容量扩充的次数。为了提高HashMap的性能:1.在使用HashMap的过程中,你比较明确它要容纳多少Entry,你应该在创建HashMap的时候直接指定它的容量; 2. 如果你确定HashMap的使用的过程中,大小会非常大,那么你应该控制好 加载因子的大小,尽量将它设置得大些。避免Entry table过大,而利用率觉很低。ja

19、vaview plaincopy print?1. /*2. *Rehashesthecontentsofthismapintoanewarraywitha3. *largercapacity.Thismethodiscalledautomaticallywhenthe4. *numberofkeysinthismapreachesitsthreshold.5. *6. *IfcurrentcapacityisMAXIMUM_CAPACITY,thismethoddoesnot7. *resizethemap,butsetsthresholdtoInteger.MAX_VALUE.8. *Th

20、ishastheeffectofpreventingfuturecalls.9. *10. *paramnewCapacitythenewcapacity,MUSTbeapoweroftwo;11. *mustbegreaterthancurrentcapacityunlesscurrent12. *capacityisMAXIMUM_CAPACITY(inwhichcasevalue13. *isirrelevant).14. */15. voidresize(intnewCapacity)16. EntryoldTable=table;17. intoldCapacity=oldTable

21、.length;18. if(oldCapacity=MAXIMUM_CAPACITY)19. threshold=Integer.MAX_VALUE;20. return;21. 22. 23. EntrynewTable=newEntrynewCapacity;24. transfer(newTable);25. table=newTable;26. threshold=(int)(newCapacity*loadFactor);27. 28. 29. /*30. *TransfersallentriesfromcurrenttabletonewTable.31. */32. voidtr

22、ansfer(EntrynewTable)33. Entrysrc=table;34. intnewCapacity=newTable.length;35. for(intj=0;jsrc.length;j+)36. Entrye=srcj;37. if(e!=null)38. srcj=null;39. do40. Entrynext=e.next;41. inti=indexFor(e.hash,newCapacity);42. e.next=newTablei;43. newTablei=e;44. e=next;45. while(e!=null);46. 47. 48. 为什么JDK

23、建议我们重写Object.equals(Object obj)方法时,需要保证对象可以返回相同的hashcode值?Java程序员都看过JDK的API文档,该文档关于Object.equals(Object obj)方法,有这样的描述: “注意:当此方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规协定,该协定声明相等对象必须具有相等的哈希码。”读者虽然知道这个协定,但是不一定真正知道为什么会有这一个要求,现在,就来看看原因吧。请读者再注意看一下上述的额put()方法实现,当遍历某个桶中的Entry链表来查找Entry实例的过程中所使用的判断条件:javavie

24、w plaincopy print?1. for(Entrye=tablei;e!=null;e=e.next)2. Objectk;3. /5.查找Entry链表中是否已经有了以Key值为Key存储的Entry对象,4. /已经存在,则将Value值覆盖到对应的Entry对象节点上5. if(e.hash=hash&(k=e.key)=key|key.equals(k)6. VoldValue=e.value;7. e.value=value;8. e.recordAccess(this);9. returnoldValue;10. 11. 对于给定的Key,Value,判断该Key是否与Entry链表中有某一个Entry对象的Key值相等

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1