频道栏目
首页 > 程序开发 > 软件开发 > 其他 > 正文
数组链表hashmap底层
2018-10-08 11:56:07           
收藏   我要投稿

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

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

(思考:这是不是和索引很像?便于查询而不便于增删)。

链表无序,除了一个链表头,其他全是依次依赖的关系,不方便查询(需要按位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

理解一下名词具体含义

点击复制链接 与好友分享!回本站首页
上一篇:java:String类的字符串判断功能
下一篇:Fiddler配置及使用总结
相关文章
图文推荐
文章
推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站