HashMap源码问题(面试二20210605)

1.HashMap源码问题:

首先我们需要熟悉下面这些知识:

数组:数组存储区间是连续的,占用的内存严重,因此空间复杂度很大,但是数组的二分查找时间复杂度很小。为O(1),数组的特点:查找速度快,删除和插入效率很低。

链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但是时间复杂度很大为O(n),链表的特点:查找的速度慢,插入和删除速度很快。

哈希表:综合了数组和链表的特性,不仅查找速度很快,插入和删除的效率也很高,哈希表有多重不同的实现方式,哈希表就是链表和数组组成的。

HashMap简介:

Java为数据结构中的映射定义了一个接口Java.util.Map,此接口主要是有四个实现类,分别是HashMap,HashTable,LinkedHashMap和TreeMap

HashMap:HashMap底层是一个Entry数组,里面存放着Node节点,Node是HashMap的内部类,每一个Node节点包括一个Key-Value键值对,HashMap也就是通过键值对来建立映射关系的,具体的我们下文再讲。HashMap把每一个Node节点占用的数组位置称为Bucket,也就是hash桶。

transient Node<K,V>[] table;
​
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
​
    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}
​

2.当两个对象的hashcode相同时会发生什么,如何获取对象

而这个问题的答案是:因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。这个时候要理解根据hashcode来划分的数组,如果数组的坐标相同,则进入链表这个数据结构中了,一般的添加都在最前面,也就是和数组下标直接相连的地方,链表长度到达8的时候,jdk1.8上升为红黑树,这样说,无疑是直接的加分项。有的面试官直接跳入数据结构,有的会直接继续挖掘

参考链接:https://blog.csdn.net/qq_43481265/article/details/84501701

3.HashMap的大小超过了负载因子定义的容量,怎么办?

【问到这个问题之后,要及时的意识到面试官要把你往线程安全的方向引入了,做好准备。】

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

参考链接:https://blog.csdn.net/qq_43481265/article/details/84501720

4.LinkedHashMap简介:

HashMap 是 Java Collection Framework 的重要成员,也是Map族(如下图所示)中我们最为常用的一种。不过遗憾的是,HashMap是无序的,也就是说,迭代HashMap所得到的元素顺序并不是它们最初放置到HashMap的顺序。HashMap的这一缺点往往会造成诸多不便,因为在有些场景中,我们确需要用到一个可以保持插入顺序的Map。庆幸的是,JDK为我们解决了这个问题,它为HashMap提供了一个子类 —— LinkedHashMap。虽然LinkedHashMap增加了时间和空间上的开销,但是它通过维护一个额外的双向链表保证了迭代顺序。特别地,该迭代顺序可以是插入顺序,也可以是访问顺序。因此,根据链表中元素的顺序可以将LinkedHashMap分为:保持插入顺序的LinkedHashMap 和 保持访问顺序的LinkedHashMap,其中LinkedHashMap的默认实现是按插入顺序排序的

 

5.我们可以使用自定义的类作为HashMap的键吗?

答:答案当然是可以的。首先Java用自定义的类作为HashMap的键需要重写hashCode()和equals()两个方法。

HashMap 中的比较 key 是先求出 key 的 hashCode 值,比较其是否相等,若相等再通过 equals ( ) 比较其Key值 是否相等 ,若相等则认为他们是相等的。若不相等则认为他们不相等。

当然不重写该2个方法的话,即使有相同的两个对象,比较也是不相等的。因为他们默认内存地址是不可能相同的。

6.为什么String,Integer这样的包装类适合作为Map的键

答:因为他们内部重写了equals()和hashCode()方法,遵循HashMap内部规范。二:他们都是final修饰的类,不可以改变,保证key的不可更改性,不会存在获取到hash值不同情况。

7.散列函数以及解决冲突的方法

问题待解决。

8. HashMap的容量为什么设置为16?

问题待处理

9.HashMap是线程安全的吗?

不是线程安全的,如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步,这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用COnnections.synchronizedMap方法来“包装”该映射。最好在创建的时候完成这一操作,以防止对映射进行意外的非同步访问。

个人觉得 HashMap 在并发时可能出现的问题主要是两方面,首先如果多个线程同时使用put方法添加元素,而且假设正好存在两个 put 的 key 发生了碰撞(根据 hash 值计算的 bucket 一样),那么根据 HashMap 的实现,这两个 key 会添加到数组的同一个位置,这样最终就会发生其中一个线程的 put 的数据被覆盖。第二就是如果多个线程同时检测到元素个数超过数组大小* loadFactor ,这样就会发生多个线程同时对 Node 数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给 table,也就是说其他线程的都会丢失,并且各自线程 put 的数据也丢失

关于这个在《Java并发编程的艺术》一书中也有体现。

关于怎么解决这个HashMap线程不安全问题,无非就是以下三种解决方案:

HashTable ConcurrentHashMap Synchronized Map 这里concurrentHashMap执行效率要高一些。

10.Hashtable和HashMap的区别?

继承:HashTable继承自Dirctionary,HashMap继承自AbstractMap,二者均实现了Map接口;

线程安全性:HashTable的方法是同步的,即是线程安全的。HaspMap的方法不是同步的,不是线程安全的的。在多线程并发的情况下,我们可以直接使用HashTable,如果 要使用HashMap,就需要自行对HashMap的同步处理。

键值:HashTable中不允许有null键和null值,HashMap中允许出现一个null键,可以存在一个或者多个键的值都为null。程序中,对于HashMap,如果使用get(参数为 键)方法时,返回结果为null,可能是该键不存在,也可能是该键对应的值为null,这就出现了结果的二义性。因此,在HashMap中,我们不能使用get()方法来查询键 对应的值,应该使用containskey()方法。 遍历:这两个在遍历的方式的实现不同,HashTable 和HashMap两者都实现了lterator。但是由于历史原因,HashTable还使用了Enumeration。

哈希值:HashTable是直接使用对象的hashCode。HashMap是重新计算hash值

扩容:HashTable和HashMap的底层实现的数组和初始大小和扩容方式。HashTable初始大小为11,并且每次扩容都为:2old+1。HashMap的默认大小为16,并且一 定是2的指数,每次扩容都为old2。

11.HashSet 和HashMap的区别?

HashSet实现了Set接口,它不允许集合中出现重复的元素,当我们当我们提到HashSet时,第一件事就是在将对象存储在HashSet之前,要确保重写hashCode()方法和equals()方法,这样才能比较对象的值是否相等,确保集合中没有储存相同的对象。如果不重写上述两个方法,那么将使用下面方法默认实现:public boolean add(Object obj)方法用在Set添加元素时,如果元素值重复时返回 "false",如果添加成功则返回"true"

HashMap实现了Map接口,Map接口对键值对进行映射。Map中不允许出现重复的键(Key)。Map接口有两个基本的实现TreeMap和HashMap。TreeMap保存了对象的排列次序,而HashMap不能。HashMap可以有空的键值对(Key(null)-Value(null))HashMap是非线程安全的(非Synchronize),要想实现线程安全,那么需要调用collections类的静态方法synchronizeMap()实现。public Object put(Object Key,Object value)方法用来将元素添加到map中。

参考链接:https://www.cnblogs.com/codercui/p/6841730.html

https://blog.csdn.net/xiaoxiaovbb/article/details/80439643

12.HashMap多线程处理之快速失败机制?

等待整理

13.以下类型为Final类型的为()

A.HashMap B. StringBuffer C.String D.HashTable

解析:final修饰的成员变量为基本数据类型时,赋值后无法改变。当final修饰的为引用变量时,在赋值后其指向地址无法改变,但对象内容可以改变。感觉应该有一定关系 另外,对于该题,final修饰类只是限定类不可被继承,而非限定了其对象是否可变

通过阅读源码可以知道,string与stringbuffer都是通过字符数组实现的。

其中string的字符数组是final修饰的,所以字符数组不可以修改。

stringbuffer的字符数组没有final修饰,所以字符数组可以修改。

string与stringbuffer都是final修饰,只是限制他们所存储的引用地址不可修改。

至于地址所指内容能不能修改,则需要看字符数组可不可以修改。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值