1、Java面试常见
1、HashMap jdk1.7和1.8中的区别
-
底层数据结构不一样,1.7中使用数组+链表,1.8使用数组+链表+红黑树。
数组默认大小为16,负载因子为0.75,如果HashMap的size大于16*0.75就要扩容了,扩容方式为创建一个新数组,大小为16乘以2,然后重新rehash操作;
1.8中当链表长度大于8(默认值)时采用红黑树,红黑树是一种自平衡二叉树,能够防止所有节点在同一侧(线性结构),从而提高查询性能(线性结构跟链表差不多,时间复制度O(n)),红黑树如何判断是左节点还是右节点,需要key实现Comparable接口; -
插入链表时不同,1.7中待插入节点总是插入链表的头部,1.8中则是插入链表的表尾。
1.7在多线程环境下扩容时会存在死循环情况,究其原因就是1.7中把节点插入到链表头部,如:链表A->B->C,第一次扩容时应该是C->B->A,如果在变成B->A时又有一个线程扩容,那么就可能出现A->B,这样就出现AB死循环了;
1.8则不会出现以上死循环情况,因为它总是插入链表表尾,顺序永远都是A->B->C; -
根据key的hashcode求数组下标时算法不一样,内部大量应用位操作(左移、右移、&等等),位操作更快,性能更好。
1.7更加复杂,因为它需要尽可能避免哈希冲突,哈希冲突多了链表就长了,链表的查询性能很差(依赖链表节点个数);
1.8因为使用了红黑树,所以冲突也无所谓,不影响查询性能,没有1.7复杂; -
1.7中节点对象使用Entry表示,1.8使用Node表示,差不多一样,属性有key、value、hash、next,红黑树使用TreeNode表示。
-
都是线程不安全的。
2、CurrentHashMap jdk1.7和1.8中的区别
-
都是线程安全的。
-
除了HashMap说明的,下面主要针对加锁不同进行说明。
1.7中引入Segment概念,分段加锁,一个segment对应几个数组节点,segment数组个数受CurrentHashMap中的concurrencyLevel值决定(默认为16),数组的默认值也为16,所以一个segment对应一个数组节点,segment继承RereentLock,存储了对应数组节点的信息;
1.8去掉Segment,直接对数组节点元素进行加锁,这样的好处是不用额外维护segment数组了(扩容时segment会变化);
3、LinkedHashMap与HashMap区别
LinkedHashMap继承于HashMap,是基于HashMap和双向链表来实现的,LinkedHashMap同样是线程不安全的;
HashMap遍历(keySet、entrySet)时是无序的,LinkedHashMap遍历时是有序的,
LinkedHashMap默认按插入顺序遍历,如果需要按访问顺序遍历则构造LinkedHashMap时指定accessOrder为true,new LinkedHashMap<>(16, 0.75f, true);
在HashMap的构造函数中,调用了init方法,而在HashMap中init方法是空实现,但LinkedHashMap重写了该方法,所以在LinkedHashMap的构造方法里,调用了自身的init方法,LinkedHashMap中的Entry对象多了before、after的Entry对象,这个双向链表是实现有序遍历的关键;
@Override
void init() {
// 创建了一个hash=-1,key、value、next都为null的Entry
header = new Entry<>(-1, null, null, null);
// 让创建的Entry的before和after都指向自身,注意after不是之前提到的next
// 其实就是创建了一个只有头部节点的双向链表
header.before = header.after = header;
}
4、TreeMap
TreeMap中的元素默认按照key的自然排序排列。
(对Integer来说,其自然排序就是数字的升序;对String来说,其自然排序就是按照字母表排序)
不使用默认的自然排序
TreeMap(Comparator comparator):创建一个空TreeMap,按照指定的comparator排序。
TreeMap能便捷的实现对其内部元素的各种排序,但其一般性能比前两种map差。
底层实现红黑树。