上面程序中使用的table其实就是一个普通数组,每个数组都有一个固定的长度,这个数组的长度就是HashMap的容量。HashMap包含如下几个构造器:
*HashMap():构建一个初始容量为16,负载因子为0.75的HashMap。
*HashMap(intinitialCapacity):构建一个初始容量为initialCapacity,负载因子为0.75的HashMap。
*HashMap(intinitialCapacity,floatloadFactor):以指定初始容量、指定的负载因子创建一个HashMap。
当创建一个HashMap时,系统会自动创建一个table数组来保存HashMap中的Entry,下面是HashMap中一个构造器的代码:
Java代码
//以指定初始化容量、负载因子创建HashMap
publicHashMap(intinitialCapacity,floatloadFactor)
{
//初始容量不能为负数
if(initialCapacity<0)
thrownewIllegalArgumentException(
"Illegalinitialcapacity:"+
initialCapacity);
//如果初始容量大于最大容量,让出示容量
if(initialCapacity>MAXIMUM_CAPACITY)
initialCapacity=MAXIMUM_CAPACITY;
//负载因子必须大于0的数值
if(loadFactor<=0||Float.isNaN(loadFactor))
thrownewIllegalArgumentException(
loadFactor);
//计算出大于initialCapacity的最小的2的n次方值。
intcapacity=1;
while(capacity this.loadFactor=loadFactor; //设置容量极限等于容量*负载因子 threshold=(int)(capacity*loadFactor); //初始化table数组 table=newEntry[capacity];//① init(); } 上面代码中粗体字代码包含了一个简洁的代码实现:找出大于initialCapacity的、最小的2的n次方值,并将其作为HashMap的实际容量(由capacity变量保存)。例如给定initialCapacity为10,那么该HashMap的实际容量就是16。 程序①号代码处可以看到:table的实质就是一个数组,一个长度为capacity的数组。 对于HashMap及其子类而言,它们采用Hash算法来决定集合中元素的存储位置。当系统开始初始化HashMap时,系统会创建一个长度为capacity的Entry数组,这个数组里可以存储元素的位置被称为“桶(bucket)”,每个bucket都有其指定索引,系统可以根据其索引快速访问该bucket里存储的元素。 无论何时,HashMap的每个“桶”只存储一个元素(也就是一个Entry),由于Entry对象可以包含一个引用变量(就是Entry构造器的的最后一个参数)用于指向下一个Entry,因此可能出现的情况是:HashMap的bucket中只有一个Entry,但这个Entry指向另一个Entry——这就形成了一个Entry链。 HashMap的读取实现 当HashMap的每个bucket里存储的Entry只是单个Entry——也就是没有通过指针产生Entry链时,此时的HashMap具有最好的性能:当程序通过key取出对应value时,系统只要先计算出该key的hashCode()返回值,在根据该hashCode返回值找出该key在table数组中的索引,然后取出该索引处的Entry,最后返回该key对应的value即可。看HashMap类的get(Kkey)方法代码: Java代码 publicVget(Objectkey) { //如果key是null,调用getForNullKey取出对应的value if(key==null) returngetForNullKey(); //根据该key的hashCode值计算它的hash码 inthash=hash(key.hashCode()); //直接取出table数组中指定索引处的值, for(Entry e!=null; //搜索该Entry链的下一个Entr e=e.next)//① { Objectk; //如果该Entry的key与被搜索key相同 if(e.hash==hash&&((k=e.key)==key ||key.equals(k))) returne.value; } returnnull; } 从上面代码中可以看出,如果HashMap的每个bucket里只有一个Entry时,HashMap可以根据索引、快速地取出该bucket里的Entry;在发生“Hash冲突”的情况下,单个bucket里存储的不是一个Entry,而是一个Entry链,系统只能必须按顺序遍历每个Entry,直到找到想搜索的Entry为止——如果恰好要搜索的Entry位于该Entry链的最末端(该Entry是最早放入该bucket中),那系统必须循环到最后才能找到该元素。 归纳起来简单地说,HashMap在底层将key-value当成一个整体进行处理,这个整体就是一个Entry对象。HashMap底层采用一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据Hash算法来决定其存储位置;当需要取出一个Entry时,也会根据Hash算法找到其存储位置,直接取出该Entry。由此可见:HashMap之所以能快速存、取它所包含的Entry,完全类似于现实生活中母亲从小教我们的:不同的东西要放在不同的位置,需要时才能快速找到它。 当创建HashMap时,有一个默认的负载因子(loadfactor),其默认值为0.75,这是时间和空间成本上一种折衷:增大负载因子可以减少Hash表(就是那个Entry数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap的get()与put()方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加Hash表所占用的内存空间。 掌握了上面知识之后,我们可以在创建HashMap时根据实际需要适当地调整loadfactor的值;如果程序比较关心空间开销、内存比较紧张,可以适当地增加负载因子;如果程序比较关心时间开销,内存比较宽裕则可以适当的减少负载因子。通常情况下,程序员无需改变负载因子的值。 如果开始就知道HashMap会保存多个key-value对,可以在创建时就使用较大的初始化容量,如果HashMap中Entry的数量一直不会超过极限容量(capacity*loadfactor),HashMap就无需调用resize()方法重新分配table数组,从而保证较好的性能。当然,开始就将初始容量设置太高可能会浪费空间(系统需要创建一个长度为capacity的Entry数组),因此创建HashMap时初始化容量设置也需要小心对待。
① 凡本网注明稿件来源为"原创"的所有文字、图片和音视频稿件,版权均属本网所有。任何媒体、网站或个人转载、链接转贴或以其他方式复制发表时必须注明"稿件来源:我考网",违者本网将依法追究责任;
② 本网部分稿件来源于网络,任何单位或个人认为我考网发布的内容可能涉嫌侵犯其合法权益,应该及时向我考网书面反馈,并提供身份证明、权属证明及详细侵权情况证明,我考网在收到上述法律文件后,将会尽快移除被控侵权内容。