来了来了,50道Java集合面试题也来啦~ 已经上传github:
https://github.com/whx123/JavaHome
1. Arraylist与LinkedList区别
可以从它们的底层数据结构、效率、开销进行阐述哈
ArrayList是数组的数据结构,LinkedList是链表的数据结构。
随机访问的时候,ArrayList的效率比较高,因为LinkedList要移动指针,而ArrayList是基于索引(index)的数据结构,可以直接映射到。
插入、删除数据时,LinkedList的效率比较高,因为ArrayList要移动数据。
LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。
3. HashMap原理,java8做了什么改变
HashMap是以键值对存储数据的集合容器
HashMap是非线性安全的。
HashMap底层数据结构:数组+(链表、红黑树),jdk8之前是用数组+链表的方式实现,jdk8引进了红黑树
Hashmap数组的默认初始长度是16,key和value都允许null的存在
HashMap的内部实现数组是Node[]数组,上面存放的是key-value键值对的节点。HashMap通过put和get方法存储和获取。
HashMap的put方法,首先计算key的hashcode值,定位到对应的数组索引,然后再在该索引的单向链表上进行循环遍历,用equals比较key是否存在,如果存在则用新的value覆盖原值,如果没有则向后追加。
jdk8中put方法:先判断Hashmap是否为空,为空就扩容,不为空计算出key的hash值i,然后看table[i]是否为空,为空就直接插入,不为空判断当前位置的key和table[i]是否相同,相同就覆盖,不相同就查看table[i]是否是红黑树节点,如果是的话就用红黑树直接插入键值对,如果不是开始遍历链表插入,如果遇到重复值就覆盖,否则直接插入,如果链表长度大于8,转为红黑树结构,执行完成后看size是否大于阈值threshold,大于就扩容,否则直接结束。
Hashmap解决hash冲突,使用的是链地址法,即数组+链表的形式来解决。put执行首先判断table[i]位置,如果为空就直接插入,不为空判断和当前值是否相等,相等就覆盖,如果不相等的话,判断是否是红黑树节点,如果不是,就从table[i]位置开始遍历链表,相等覆盖,不相等插入。
HashMap的get方法就是计算出要获取元素的hash值,去对应位置获取即可。
HashMap的扩容机制,Hashmap的扩容中主要进行两步,第一步把数组长度变为原来的两倍,第二部把旧数组的元素重新计算hash插入到新数组中,jdk8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二部一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。
HashMap大小为什么是2的幂次方?效率高+空间分布均匀
有关于HashMap这些常量设计目的,也可以看我这篇文章: 面试加分项-HashMap源码中这些常量的设计目的
4. List 和 Set,Map 的区别
List 以索引来存取元素,有序的,元素是允许重复的,可以插入多个null。
Set 不能存放重复元素,无序的,只允许一个null
Map 保存键值对映射,映射关系可以一对一、多对一
List 有基于数组、链表实现两种方式
Set、Map 容器有基于哈希存储和红黑树两种方式实现
Set 基于 Map 实现,Set 里的元素值就是 Map的键值
6. HashMap,HashTable,ConcurrentHash的共同点和区别
HashMap
底层由链表+数组+红黑树实现
可以存储null键和null值
线性不安全
初始容量为16,扩容每次都是2的n次幂
加载因子为0.75,当Map中元素总数超过Entry数组的0.75,触发扩容操作.
并发情况下,HashMap进行put操作会引起死循环,导致CPU利用率接近100%
HashMap是对Map接口的实现
HashTable
HashTable的底层也是由链表+数组+红黑树实现。
无论key还是value都不能为null
它是线性安全的,使用了synchronized关键字。
HashTable实现了Map接口和Dictionary抽象类
Hashtable初始容量为11
ConcurrentHashMap
ConcurrentHashMap的底层是数组+链表+红黑树
不能存储null键和值
ConcurrentHashMap是线程安全的
ConcurrentHashMap使用锁分段技术确保线性安全
JDK8为何又放弃分段锁,是因为多个分段锁浪费内存空间,竞争同一个锁的概率非常小,分段锁反而会造成效率低。
7. 写一段代码在遍历 ArrayList 时移除一个元素
因为foreach删除会导致快速失败问题,fori顺序遍历会导致重复元素没删除,所以正确解法如下:
第一种遍历,倒叙遍历删除
need-to-insert-img
for(int i=list.size()-1; i>-1; i--){ if(list.get(i).equals("jay")){ list.remove(list.get(i)); }}
第二种,迭代器删除
need-to-insert-img
Iterator itr = list.iterator();while(itr.hasNext()) { if(itr.next().equals("jay") { itr.remove(); }}
9. TreeMap底层?
TreeMap实现了SotredMap接口,它是有序的集合。
TreeMap底层数据结构是一个红黑树,每个key-value都作为一个红黑树的节点。
如果在调用TreeMap的构造函数时没有指定比较器,则根据key执行自然排序。
10. HashMap 的扩容过程
Hashmap的扩容:
第一步把数组长度变为原来的两倍,
第二步把旧数组的元素重新计算hash插入到新数组中。
jdk8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二步一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。
11. HashSet是如何保证不重复的
可以看一下HashSet的add方法,元素E作为HashMap的key,我们都知道HashMap的可以是不允许重复的,哈哈。
need-to-insert-img
public boolean add(E e) { return map.put(e, PRESENT)==null;}
12. HashMap 是线程安全的吗,为什么不是线程安全的?死循环问题?
不是线性安全的。
并发的情况下,扩容可能导致死循环问题。
14. 哪些集合类是线程安全的?哪些不安全?
线性安全的
Vector:比Arraylist多了个同步化机制。
Hashtable:比Hashmap多了个线程安全。
ConcurrentHashMap:是一种高效但是线程安全的集合。
Stack:栈,也是线程安全的,继承于Vector。
线性不安全的
Hashmap
Arraylist
LinkedList
HashSet
TreeSet
TreeMap
15. ArrayList 和 Vector 的区别是什么?
Vector是线程安全的,ArrayList不是线程安全的。
ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
Vector只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性。
18. 如何实现数组和 List之间的转换?
List 转 Array
List 转Array,必须使用集合的 toArray(T[] array),如下:
need-to-insert-img
List<String> list = new ArrayList<String>();list.add("jay");list.add("tianluo");// 使用泛型,无需显式类型转换String[] array = list.toArray(new String[list.size()]);System.out.println(array[0]);
如果直接使用 toArray 无参方法,返回值只能是 Object[] 类,强转其他类型可能有问题,demo如下:
need-to-insert-img
List<String> list = new ArrayList<String>();list.add("jay");list.add("tianluo");String[] array = (String[]) list.toArray();System.out.println(array[0]);
运行结果:
need-to-insert-img
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String; at Test.main(Test.java:14)
Array 转List
使用Arrays.asList() 把数组转换成集合时,不能使用修改集合相关的方法啦,如下:
need-to-insert-img
String[] str = new String[] { "jay", "tianluo" };List list = Arrays.asList(str);list.add("boy");
运行结果如下:
need-to-insert-img
Exception in thread "main" java.lang.UnsupportedOperationException at java.util.AbstractList.add(AbstractList.java:148) at java.util.AbstractList.add(AbstractList.java:108) at Test.main(Test.java:13)
因为 Arrays.asList不是返回java.util.ArrayList,而是一个内部类ArrayList。
可以这样使用弥补这个缺点:
need-to-insert-img
//方式一:ArrayList< String> arrayList = new ArrayList<String>(strArray.length);Collections.addAll(arrayList, strArray);//方式二:ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)) ;
26. Java 中的 LinkedList是单向链表还是双向链表?
哈哈,看源码吧,是双向链表
27. 说一说ArrayList 的扩容机制吧
ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。
28. HashMap 的长度为什么是2的幂次方,以及其他常量定义的含义~
为了能让HashMap存取高效,数据分配均匀。
看着呢,以下等式相等,但是位移运算比取余效率高很多呢~
need-to-insert-img
hash%length=hash&(length-1)
可以看下我这篇文章哈~ 面试加分项-HashMap源码中这些常量的设计目的
29. ConcurrenHashMap 原理?1.8 中为什么要用红黑树?
聊到ConcurrenHashMap,需要跟面试官聊到安全性,分段锁segment,为什么放弃了分段锁,与及选择CAS,其实就是都是从效率和安全性触发,嘻嘻~
need-to-insert-img
java8不是用红黑树来管理hashmap,而是在hash值相同的情况下(且重复数量大于8),用红黑树来管理数据。红黑树相当于排序数据。可以自动的使用二分法进行定位。性能较高。
30. ArrayList的默认大小
ArrayList 的默认大小是 10 个元素
33. 我们如何对一组对象进行排序?
可以用 Collections.sort()+ Comparator.comparing(),因为对对象排序,实际上是对对象的属性排序哈~
n
38. 如果想用Object作为hashMap的Key?;
重写hashCode()和equals()方法啦~ (这个答案来自互联网哈~)
重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性;
42. HashSet和TreeSet有什么区别?
Hashset 的底层是由哈希表实现的,Treeset 底层是由红黑树实现的。
HashSet中的元素没有顺序,TreeSet保存的元素有顺序性(实现Comparable接口)
HashSet的add(),remove(),contains()方法的时间复杂度是O(1);TreeSet中,add(),remove(),contains()方法的时间复杂度是O(logn)
43. Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()?
元素重复与否是使用equals()方法进行判断的,这个可以跟面试官说说==和equals()的区别,hashcode()和equals
44. 说出ArrayList,LinkedList的存储性能和特性
这道面试题,跟ArrayList,LinkedList,就是换汤不换药的~
ArrayList,使用数组方式存储数据,查询时,ArrayList是基于索引(index)的数据结构,可以直接映射到,速度较快;但是插入数据需要移动数据,效率就比LinkedList慢一点~
LinkedList,使用双向链表实现存储,按索引数据需要进行前向或后向遍历,查询相对ArrayList慢一点;但是插入数据速度较快。
LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。
45. HashMap在JDK1.7和JDK1.8中有哪些不同?
存储结构不同:JDK1.7是数组+链表,JDK1.8则是数组+链表+红黑树结构;
初始化方式不同:JDK1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而JDK1.8则是直接调用resize()扩容;
插入数据方式不同:插入键值对的put方法的区别,JDK1.8中会将节点插入到链表尾部,而JDK1.7中是采用头插,因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。;
hash值计算不同:JDK1.7采用9次扰动(4次位运算+5次异或运算),JDK1.8采用2次扰动(1次位运算+1次异或运算)
扩容时JDK1.8会保持原链表的顺序,而JDK1.7会颠倒链表的顺序;而且JDK1.8是在元素插入后检测是否需要扩容,JDK1.7则是在元素插入前;
扩容后数据存储位置的计算方式不同:1. 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1)
扩容策略不同:JDK1.7中是只要不小于阈值就直接扩容2倍;而JDK1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数大于6就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容。
————————————————
版权声明:本文为CSDN博主「晓二当家」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40397586/article/details/120869564
48. ArrayList 和 HashMap 的默认大小是多数?
在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。
50. HashMap是怎么解决哈希冲突的
Hashmap解决hash冲突,使用的是链地址法,即数组+链表的形式来解决。put执行首先判断table[i]位置,如果为空就直接插入,不为空判断和当前值是否相等,相等就覆盖,如果不相等的话,判断是否是红黑树节点,如果不是,就从table[i]位置开始遍历链表,相等覆盖,不相等插入。