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修饰,只是限制他们所存储的引用地址不可修改。
至于地址所指内容能不能修改,则需要看字符数组可不可以修改。