数组链表hashmap底层

数组与链表的区别:数组是有序的,方便查询(数组下标是依次递增的,因此可以根据下标来进行二叉树查询)

但是不便于新增或删除,每当插入或删除一个元素时,之后的元素就会重新排列位置获得新的下标。

(思考:这是不是和索引很像?便于查询而不便于增删)。
链表无序,除了一个链表头,其他全是依次依赖的关系,不方便查询(需要按位next()),便于新增删除,不需要排位置。

数组原理的类有:arraylist
链表原理的类有:linklist

这里arraylist可以根据size来循环,而linklist需要迭代器来进行next的操作,可以证明二者的不同性质。

linklist迭代
hashmap是由数组和链表的原理共同组成

hashmap的key-value模式和数组的index-value模式很相似;

而在key不同而value相同时候,对一个value的储存方式与链表很相似;


哈希表((Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。
哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

 


首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。


// 存储时:
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;


// 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];


hashmap的put原理(参数:key,value):
根据key算出hashcode值,算出该code在哈希表中的位置,
遍历该链表,如果该key存在,就替换为新value。


hashmap的get原理(参数:key):
根据key查出hashcode值,定位到数组元素,依次遍历链表next,直到有value。
 


hashmap的扩容机制:
1、
当我们往hashmap中put元素的时候,先根据key的hash值得到这个元素在 数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。
如果这个元素所在的位子上已经存放有其他元素了,那么在同一个位子上的元素将以链表的 形式存放,新加入的放在链头,比如a->b->c,新加入的d放到a的位置前面,
最先加入的放在链尾,也就是c。最后变成d->a->b->c,从hashmap中get元素时,
首先计算key的hashcode,找到数组中对应位置的某一元素, 然后通过key的equals方法在对应位置的链表中找到需要的元素。


2、
在hashmap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。
如何计算这个位置就是hash算法。前面说过hashmap的数据结构是数组和链表的结合,
所以我们当然希望这个hashmap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,
那么当我们用hash算法求得这个位置 的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表。
所以我们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。
但是,“模”运算的消耗还是比较大的,能不能找一种更快速,消耗更小的方式那?java中时这样做的,


Java代码


staticintindexFor(inth,intlength){ 
returnh&(length-1); 
}


首 先算得key得hashcode值,然后跟数组的长度-1做一次“与”运算(&)。看上去很简单,
其实比较有玄机。比如数组的长度是2的4次方, 那么hashcode就会和2的4次方-1做“与”运算。
很多人都有这个疑问,为什么hashmap的数组初始化大小都是2的次方大小时,hashmap 的效率最高,我以2的4次方举例,来解释一下为什么数组大小为2的幂时hashmap访问的性能最高。 
看下图,左边两组是数组长度为16(2的4次方),右边两组是数组长度为15。两组的hashcode均为8和9,但是很明显,当它们和1110“与”的 时候,产生了相同的结果,
也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到同一个链表上,那么查询的时候就需要遍历这个链 表,得到8或者9,这样就降低了查询的效率。
同时,我们也可以发现,当数组长度为15的时候,hashcode的值会与14(1110)进行“与”,
那么 最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,
更糟的是 这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!
所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,
相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。说到这里,我们再回头看一下hashmap中默认的数组大小是多少,
查看源代码可以得知是16,为什么是16,而不是15,也不是20呢,看到上面 annegu的解释之后我们就清楚了吧,显然是因为16是2的整数次幂的原因,
在小数据量的情况下16比15和20更能减少key之间的碰撞,而加快查询 的效率。


3、


当hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对hashmap的数组进行 扩容,
数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了, 
而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。


那么hashmap什么时候进行扩容呢?当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的 默认值为0.75,
也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,
然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那 么预设元素的个数能够有效的提高hashmap的性能。


比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,
即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 
也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。


哈希表及处理hash冲突的方法:


看了ConcurrentHashMap的实现, 使用的是拉链法.


虽然我们不希望发生冲突,但实际上发生冲突的可能性仍是存在的。当关键字值域远大于哈希表的长度,而且事先并不知道关键字的具体取值时。冲突就难免会发生。


另外,当关键字的实际取值大于哈希表的长度时,而且表中已装满了记录,如果插入一个新记录,不仅发生冲突,而且还会发生溢出。


因此,处理冲突和溢出是哈希技术中的两个重要问题。


哈希法又称散列法、杂凑法以及关键字地址计算法等,相应的表称为哈希表。
这种方法的基本思想是:首先在元素的关键字k和元素的存储位置p之间建立一个对应关系f,使得p=f(k),f称为哈希函数。
创建哈希表时,把关键字为k的元素直接存入地址为f(k)的单元;以后当查找关键字为k的元素时,再利用哈希函数计算出该元素的存储位置p=f(k),
从而达到按关键字直接存取元素的目的。 


当关键字集合很大时,关键字值不同的元素可能会映象到哈希表的同一地址上,即 k1≠k2 ,但 H(k1)=H(k2),这种现象称为冲突,
此时称k1和k2为同义词。实际中,冲突是不可避免的,只能通过改进哈希函数的性能来减少冲突。
综上所述,哈希法主要包括以下两方面的内容:
 1)如何构造哈希函数


 2)如何处理冲突。


总结一下问题:
hashmap原理
key,value存储原理
同一个链表中的value如何区分
如何扩容:(元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的 默认值为0.75,每次扩容默认扩充一倍)
hashmap处理冲突是否与同一个链表中的value如何区分为同一个问题
为什么HashMap容量一定要为2的幂呢


entry 
hashtable
理解一下名词具体含义


 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值