HashMap经典面试题
1.为什么加载因子是0.75?
如果设置为1:空间利用率得到了很大的满足,很容易碰撞,产生链表,从而导致查询效率低
如果设置为0.5:碰撞的概率低,扩容,产生链表的几率低,查询效率高,空间利用率太低
所以做了一个折中,让空间利用率和时间效率都得到满足
2.主数组的长度为什么是2^n?
原因1: h & (lengh -1) 等效于 h % length操作,等效的前提就是:length必须是2的整数倍
原因2:防止哈希冲突,位置冲突
集合面试题
HashMap和Hashtable的区别
1.线程是否安全方面:HashMap是非线程安全的,hashtable是线程安全的。hashtable内部的方法基本都经过了synchronized修饰(如果你要保证线程安全的话就使用ConcurrentHashMap吧)
2.效率方面:因为线程安全的问题,HashMap要比hashtable效率高一点,另外hashtable基本被淘汰了,不要在代码使用它
3.key-value支持的方面:HashMap中,null可以作为key,这样的key只有一个,可以有一个或者多个值为null。但是在hashtable中,put进的key-value只要有一个null,直接抛出空指针异常
4.初始容量大小和每次扩充容量大小的不同方面:(1)创建时如果不指定初始容量,HashMap默认初始化大小为16,hashtable默认为11,扩容方面,HashMap每次2,hashtable每次2+1。(2)如果给定了容量初始值,那么hashtable会直接使用你给的初始值,但是HashMap会将其扩充到2的幂次方大小(HashMap中的tableSizeFor()方法保证),也就是说为什么HashMap总是使用2的幂作为哈希表的大小
5.底层数据结构:HashMap在解决哈希冲突时候有较大变化,当链表长度大于8而且table大小>=64时,链表转为红黑树以减少搜索时间。hashtable则没有这样的机制
HashMap底层实现
jdk1.8之前
hashmap底层是 数组 + 链表结合在一起 也就是链表散列。hashmap通过key 的hashCode经过扰动函数处理后得到hash值,然后通过(数组length - 1) & hash 判断当前元素存放的位置,如果当前位置存在元素的话,就判断该元素与要存入的元素的hash和key是否相等(equals方法判断是true还是false),如果相等,则直接覆盖,不相等就通过拉链法解决冲突
所谓“拉链法”就是:将链表和数组相结合,也就是创建一个链表数组,数组中每一位置就是一个链表,若遇到哈希冲突,则将冲突的值加入到链表中即可
所谓扰动函数就是hashMap的hash方法,使用hash方法也就是扰动函数为了防止一些实现比较差的hashCode()方法,换句话说扰动函数可以减少碰撞((h = key.hashCode()) ^ (h >>> 16))
jdk1.8 的hash方法比1.7的更加简化,但是原理不变
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
jdk1.8之后在解决哈希冲突,当链表长度>8并且此时table大小>=64,链表转红黑树,以减少搜索时间
ConcurrentHashMap底层具体实现原理?
jdk1.7
1.首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问
2.在jdk1.7中,ConcurrentHashMap采用segment + HashEntry的方式实现,结构如下:
3.一个ConcurrentMap里保护一个Segment数组。Segment数组的结构和HashMap类似,是一种数组和链表结构,一个Segment包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,segment的锁是一种可重入锁,当对HashEntry数组的数据进行修改时,必须首先获得segment的锁
jdk1.8中,放弃了segment臃肿的设计,取而代之使用的是 Node + CAS + Synchronized 来保证并发安全进行,synchronized只能锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会并发,效率又提高了
Java集合的快速失败机制“fail-fast”
1.是java集合中的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制
2.例如:假设存在两个线程(A,B),A通过iterator在遍历集合中的元素,在某个时候B修改了集合的结构(是结构上的修改,不单是简单修改集合的元素内容),那么这个时候程序会抛出ConcurrentModificationException异常,从而产生fail-fast机制
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量,集合在被遍历期间如果内容发生变化,就会改变modCount值。每当迭迭代器使用hasNext()或者next()遍历下一个元素之前,都会检测modCount变量是否为excepctedmodCount的值,是的话就返回遍历,否则抛出异常,终止遍历
解决办法:
1.在遍历过程中,所有涉及到改变modCount值的地方全部加上synchronized
2.使用CopyOnWriteArrayList来替换ArrayList
怎么确保一个集合不能被修改?
可以使用Collections.unmodifiableCollection(Collection c)方法来创建一个只读集合,这样改变集合的任何操作都会抛出异常
ArrayList和LinkedList区别是什么?
1.数据结构实现:ArrayList是动态数组的数据结构实现,而LinkedList是双向链表的数据结构实现
2.随机访问效率:ArryayList比LinkedList在随机访问的时候效率高,因为LinkedList是线性的数据村出发昂视,所以需要移动指针查找
3.增加和删除效率:在非首位的增加和删除操作,LinkedList要比ArrayList效率高,因为ArrayList增删操作要影响数组内其他元素的下标
4.内存空间占用:LinkedList比ArrayList更占内存,因为LinkedList的结点除了存储数据,还存储了两个引用分别指向前一个元素和后一个元素
5.线程安全:两个都是不能保证线程安全的
6.综合来说:频繁读取集合中的元素,使用ArrayList,插入和删除较多使用LinkedList
Map底层为什么使用了红黑树?
红黑树是一种二叉查找树,但在每一个节点增加一个存储位表示节点的颜色,要么是红要么是黑。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制。红黑树确保没有一条路径会比其他路径长处两倍。因此,红黑树是一张弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数较少,所以对于搜索,插入,删除操作较多的情况下,通常使用红黑树