HashMap相关问题

1.map的实现类有哪些?
(1).HashMap作为Map的主要实现类,线程不安全的,效率高,可以存储null的key和value;
Java中的hashcode和equals
 1.关于hashcode
    1.hashcode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashcode是用来在散列存储结构中确定对象的存储地址的;
    2.如果两个对象相同,就是适用于equals方法,那么这两个对象的hashcode一定要相同;
 3.如果对象的equals方法被重写,那么对象的hashcode也尽量重写,并且产生hashcode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第二点;
4.两个对象的hashcode相同,并不一定表示两个对象就相同,也就是不一定适用于equals方法,只能够说明这两个对象在散列存储结构中,如hashtable,他们就是存放在同一个篮子里;
  2.关于equals
    ==用于比较引用和比较基本数据类型时具有不同的功能:比较基本数据类型,如果两个值相同,则结果为true,而在比较引用时,如果引用指向内存中的同一对象,结果为true。
    equals()作为方法,实现对象的比较。由于==运算符不允许我们进行覆盖,也就是说它限制了我们的表达,因此我们复写equals,达到比较对象内容是否相同的目的,这些是==运算符做不到的。

HashMap的实现原理

  1.HashMap概述

    HashMap是基于哈希表的Map接口的非同步实现,此实现提供所有可选的映射操作。并允许使用null值和null键,此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
    在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外,HashMap实际上是一个链表散列的数据结构,即数组和链表的结合体。
hashmap数组的初始值是16,负载因子是0.75,阙值是12,16*0.75=12,当然初始值和负载因子都是默认的值;hashmap发生扩容的条件:
从代码可以看出扩容的两个条件是:当前size是否大于阙值以及是否发生hash碰撞,hash碰撞的意思就是当前查到的数组是否有值;因为数组扩容需要数组扩大2倍,还需要将原数组的数据写到新数组中,是一个非常耗时的行为;所以如果大约知道会有多少数据,就给定一个大一点的初始值,尽量少进行数组的扩容;如果某个数组中的链表的长度大于8,会转变为红黑树,如果再次小于8,会变成单向链表;
图片说明

一、首先介绍下负载因子的作用

对于HashMap的研究,我之前一直停留在烤炉源码的实现,其实现在看来,系统默认的各种参数才是HashMap的精华所在。

负载因子是和扩容机制有关的,意思时如果当前容器的容量达到了我们设定的最大值,就要开始执行扩容操作。举例说明下

比如说当前的容器容量是16,负载因子是0.75,16*0.75=12,也就是说,当容量达到了12的时候就会进行扩容操作

他的作用很简单,相当于是一个扩容机制的阈值。当超过了这个阈值,就会触发扩容机制。HashMap源码已经为我们默认指定了负载因子是0.75。
我截取了部分源码,从这里可以看出,系统默认的负载因子值就是0.75,而且我们还可以在构造方法中去指定。下面我们就正式来分析一下为什么是默认的0.75。

二、原因解释
我们在考虑HashMap的时候,首先要想到的是HashMap只是一个数据结构,既然是数据结构最主要的就是节省时间和空间。负载因子的作用肯定也是节省时间和空间。因为是什么呢?首先我们考虑下两种极端情况。

1、负载因子是1.0
首先看下HashMap的底层数据结构

在这里插入图片描述
我们的数据一开始是保存在数组里面的,当发生了Hash碰撞的时候,就是在这个数据节点上,生出一个链表,当链表长度达到一定长度的时候,就会把链表转化为红黑树。

当负载因子是1.0的时候,也就意味着,只有当数组的8个值(这个图表示了8个)全部填充了,才会发生扩容。这就带来了很大的问题,因为Hash冲突时避免不了的。当负载因子是1.0的时候,意味着会出现大量的Hash的冲突,底层的红黑树变得异常复杂。对于查询效率极其不利。这种情况就是牺牲了时间来保证空间的利用率。

因此一句话总结就是负载因子过大,虽然空间利用率上去了,但是时间效率降低了。

2、负载因子是0.5
负载因子是0.5的时候,这也就意味着,当数组中的元素达到了一半就开始扩容,既然填充的元素少了,Hash冲突也会减少,那么底层的链表长度或者是红黑树的高度就会降低。查询效率就会增加。
但是,兄弟们,这时候空间利用率就会大大的降低,原本存储1M的数据,现在就意味着需要2M的空间。
一句话总结就是负载因子太小,虽然时间效率提升了,但是空间利用率降低了。

3、负载因子0.75
经过前面的分析,基本上为什么是0.75的答案也就出来了,这是时间和空间的权衡。
大致意思就是说负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。

Hashmap为什么是线程不安全的:
1、put的时候导致的多线程数据不一致。
这个问题比较好想象,比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。

2、另外一个比较明显的线程不安全的问题是HashMap的get操作可能因为resize而引起死循环(cpu100%)

(2).LinkedHashMap:是HashMap的子类,保证在遍历map元素时,可以按照添加的顺序实现遍历,对于频繁的遍历操作,它的执行效率高于HashMap.
原因:在原有的HashMap底层结构的基础上,添加了一对指针,指向前一个和后一个元素。
(3).TreeMap:保证按照添加的key-value对进行排序,实现排序遍历,此时考虑key的自然排序或者定制排序。底层使用红黑树;
(4).Hashtable作为古老的实现类,线程安全,效率低,不可以存储null的key和value。底层都使用哈希表结构,查询速度快。
(5).Properties:是Hashtable的子类,常用来处理配置文件。key和value都是String类型的。存取数据时,建议使用setProperty(String key,String value)和getProperty(String key)

全部评论

相关推荐

要AC不要WA:投一天,喜提两笔试
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务