目录
前言
一、集合容器概述
1. 什么是集合
2. 集合的特点
3. 集合和数组的区别
4. 使用集合框架的好处
5. 常用的集合类有哪些?
6. List,Set,Map三者的区别?
7. 集合框架底层数据结构
8. 哪些集合类是线程安全的?
9. Java集合的快速失败机制 “fail-fast”?
10. 怎么确保一个集合不能被修改?
List接口
11. 迭代器 Iterator 是什么?
12. Iterator 怎么使用?有什么特点?
13. 如何边遍历边移除 Collection 中的元素?
14. Iterator 和 ListIterator 有什么区别?
15. 遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?
16. 说一下 ArrayList 的优缺点
17. 如何实现数组和 List 之间的转换?
18. ArrayList 和 LinkedList 的区别是什么?
19. ArrayList 和 Vector 的区别是什么?
20. 插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性?
21. 多线程场景下如何使用 ArrayList?
22. 为什么 ArrayList 的 elementData 加上 transient 修饰?
23. List 和 Set 的区别
Set接口
24. 说一下 HashSet 的实现原理?
25. HashSet如何检查重复?HashSet是如何保证数据不可重复的?
26. HashSet与HashMap的区别
三、Map接口
27. 什么是Hash算法
28. 什么是链表
29. 说一下HashMap的实现原理?
30. HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现
31. 什么是红黑树
32. HashMap的put方法的具体流程?
33. HashMap的扩容操作是怎么实现的?
34. HashMap是怎么解决哈希冲突的?
35. 能否使用任何类作为 Map 的 key?
36. 为什么HashMap中String、Integer这样的包装类适合作为K?
37. 如果使用Object作为HashMap的Key,应该怎么办呢?
38. HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
39. HashMap 的长度为什么是2的幂次方
40. HashMap 与 HashTable 有什么区别?
41. 什么是TreeMap 简介
42. 如何决定使用 HashMap 还是 TreeMap?
43. HashMap 和 ConcurrentHashMap 的区别
44. ConcurrentHashMap 和 Hashtable 的区别?
45. ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?
四、辅助工具类
46. Array 和 ArrayList 有何区别?
47. 如何实现 Array 和 List 之间的转换?
48. comparable 和 comparator的区别?
49. Collection 和 Collections 有什么区别?
50. TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
51. Collection 和 Collections 有什么区别?
最后
马上到今年的金三银四了,又是跳槽的好季节,准备跳槽的同学都摩拳擦掌准备大面好几场,本次小编为大家准备了精选的 Java 集合面试题,快来查漏补缺吧。
小编分享的这份金三银四Java后端开发面试总结包含了JavaOOP、Java集合容器、Java异常、并发编程、Java反射、Java序列化、JVM、Redis、Spring MVC、MyBatis、MySQL数据库、消息中间件MQ、Dubbo、Linux、ZooKeeper、 分布式&数据结构与算法等26个专题技术点,都是小编在各个大厂总结出来的面试真题,已经有很多粉丝靠这份PDF拿下众多大厂的offer,今天在这里总结分享给到大家!【持续更新中!】
完整版Java面试题地址:2021最新面试题合集集锦。
序号 | 专题 | 内容 | 链接地址 |
1 | 中间件 | 【金三银四】Java中间件面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/114002362 |
2 | 微服务 | 【金三银四】Java微服务面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/113923549 |
3 | 并发编程 | 【金三银四】Java并发编程面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/113895576 |
4 | Java基础 | 【金三银四】Java基础知识面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115146056 |
5 | Spring Boot | 【金三银四】Spring Boot面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115186811 |
6 | Redis | 【金三银四】Redis面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115188010 |
7 | Spring MVC | 【金三银四】Spring MVC面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115220638 |
8 | Spring Cloud | 【金三银四】Spring Cloud面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115220987 |
9 | MySQL优化 | 【金三银四】MySQL优化面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115254620 |
10 | JVM | 【金三银四】JVM性能调优面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115283079 |
11 | Linux | 【金三银四】Linux面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115283583 |
12 | Mybatis | 【金三银四】Mybatis面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115285732 |
13 | 网络编程 | 【金三银四】TCP,UDP,Socket,Http网络编程面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115464896 |
14 | 设计模式 | 【金三银四】设计模式面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115466449 |
15 | 大数据 | 金三银四】大数据面试题100道(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115484939 |
16 | Tomcat | 【金三银四】Tomcat面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115486648 |
17 | 多线程 | 【金三银四】多线程面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115487212 |
18 | Nginx | 【金三银四】Nginx_BIO_NIO_AIO面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115488446 |
19 | memcache | 【金三银四】memcache面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115494213 |
20 | java异常 | 【金三银四】java异常面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115530401 |
21 | Java虚拟机 | 【金三银四】Java虚拟机面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115532365 |
22 | Java集合 | 【金三银四】Java集合面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115599284 |
23 | Git常用命令 | 【金三银四】Git常用命令(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115602390 |
24 | Elasticsearch | 【金三银四】Elasticsearch面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115604293 |
25 | Dubbo | 【金三银四】Dubbo面试题(2021最新版) | https://blog.csdn.net/SQY0809/article/details/115605560 |
集合的特点主要有如下两点:
1. 容量自增长;
2. 提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;
3. 可以方便地扩展或改写集合,提高代码复用性和可操作性。
4. 通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。
List list = new ArrayList<>();
list. add("x");
Collection clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());
二、Collection接口
Iterator 使用代码如下:
List list = new ArrayList<>();
Iterator it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModifificationException 异常。
// list to array
List list = new ArrayList();
list.add("123");
list.add("456");
list.toArray();
// array to list
String[] array = new String[]{"123","456"};
Arrays.asList(array);
List synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}
private transient Object[] elementData;
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
private void writeObject(java.io.ObjectOutputStream s) throws
java.io.IOException{
*// Write out element count, and any hidden stuff*
int expectedModCount = modCount;
s.defaultWriteObject();
*// Write out array length*
s.writeint(elementData.length);
*// Write out all elements in the proper order.*
for (int i=0; i
private static final Object PRESENT = new Object();
private transient HashMap map;
public HashSet() {
map = new HashMap<>();
}
public Boolean add(E e) {
// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
return map.put(e, PRESENT)==null;
}
说道红黑树先讲什么是二叉树
红黑树
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//实现Map.put和相关方法
final V putVal(int hash, K key, V value, Boolean onlyIfAbsent,
Boolean evict) {
Node[] tab;
Node p;
int n, i;
// 步骤①:tab为空则创建
// table未初始化或者长度为0,进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 步骤②:计算index,并对null做处理
// (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数
组中)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 桶中已经存在元素 else {
Node e;
K k;
// 步骤③:节点key存在,直接覆盖value
// 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 将第一个元素赋值给e,用e来记录
e = p;
// 步骤④:判断该链为红黑树
// hash值不相等,即key不相等;为红黑树结点
// 如果当前元素类型为TreeNode,表示为红黑树,putTreeVal返回待存放的node, e可能为null else if (p instanceof TreeNode)
// 放入树中
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
// 步骤⑤:该链为链表
// 为链表结点 else {
// 在链表最末插入结点
for (int binCount = 0; ; ++binCount) {
// 到达链表的尾部
//判断该链表尾部指针是不是空的
if ((e = p.next) == null) {
// 在尾部插入新结点
p.next = newNode(hash, key, value, null);
//判断链表的长度是否达到转化红黑树的临界值,临界值为8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//链表结构转树形结构
treeifyBin(tab, hash);
// 跳出循环
break;
}
// 判断链表中结点的key值与插入的元素的key值是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 相等,跳出循环
break;
// 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
p = e;
}
}
//判断当前的key已经存在的情况下,再来一个相同的hash值、key值时,返回新来的value这个
值
if (e != null) {
// 记录e的value
V oldValue = e.value;
// onlyIfAbsent为false或者旧值为null
if (!onlyIfAbsent || oldValue == null)
//用新值替换旧值
e.value = value;
// 访问后回调
afterNodeAccess(e);
// 返回旧值
return oldValue;
}
}
// 结构性修改
++modCount;
// 步骤⑥:超过最大容量就扩容
// 实际大小大于阈值则扩容
if (++size > threshold)
resize();
// 插入后回调
afterNodeInsertion(evict);
return null;
}
1. 判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
2. 根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
3. 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
4. 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向5;
5. 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
6. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
final Node[] resize() {
Node[] oldTab = table;
//oldTab指向hash桶数组
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//如果oldCap不为空的话,就是hash桶数组不为空
if (oldCap >= MAXIMUM_CAPACITY) {
//如果大于最大容量了,就赋值为整数最大的阀值
threshold = Integer.MAX_VALUE;
return oldTab;
//返回
}
//如果当前hash桶数组的长度在扩容后仍然小于最大容量 并且oldCap大于默认值16 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
// double threshold 双倍扩容阀值threshold
}
// 旧的容量为0,但threshold大于零,代表有参构造有cap传入,threshold已经被初始化
成最小2的n次幂
// 直接将该值赋给新的容量 else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
// 无参构造创建的map,给出默认容量和threshold 16, 16*0.75 else {
// zero initial threshold signifies using
defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 新的threshold = 新的cap * 0.75
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft <
(float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
// 计算出新的数组长度后赋给当前成员变量table
@SuppressWarnings({
"rawtypes","unchecked"
}
)
Node[] newTab = (Node[])new Node[newCap];
//新建hash桶数组
table = newTab;
//将新数组的值复制给旧的hash桶数组
// 如果原先的数组没有初始化,那么resize的初始化工作到此结束,否则进入扩容元素重排逻辑,使
其均匀的分散
if (oldTab != null) {
// 遍历新数组的所有桶下标
for (int j = 0; j < oldCap; ++j) {
Node e;
if ((e = oldTab[j]) != null) {
// 旧数组的桶下标赋给临时变量e,并且解除旧数组中的引用,否则就数组无
法被GC回收
oldTab[j] = null;
// 如果e.next==null,代表桶中就一个元素,不存在链表或者红黑树
if (e.next == null)
// 用同样的hash映射算法把该元素加入新的数组
newTab[e.hash & (newCap - 1)] = e;
// 如果e是TreeNode并且e.next!=null,那么处理树中元素的重排 else if (e instanceof TreeNode)
((TreeNode)e).split(this, newTab, j, oldCap);
// e是链表的头并且e.next!=null,那么处理链表中元素重排 else {
// preserve order
// loHead,loTail 代表扩容后不用变换下标,见注1
Node loHead = null, loTail = null;
// hiHead,hiTail 代表扩容后变换下标,见注1
Node hiHead = null, hiTail = null;
Node next;
// 遍历链表
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
// 初始化head指向链表当前元素e,e不一定是链表的
第一个元素,初始化后loHead
// 代表下标保持不变的链表的头元素
loHead = e; else
// loTail.next指向当前e
loTail.next = e;
// loTail指向当前的元素e
// 初始化后,loTail和loHead指向相同的内存,所以当
loTail.next指向下一个元素时,
// 底层数组中的元素的next引用也相应发生变化,造成lowHead.next.next.....
// 跟随loTail同步,使得lowHead可以链接到所有属于该链
表的元素。
loTail = e;
} else {
if (hiTail == null)
// 初始化head指向链表当前元素e, 初始化后hiHead
代表下标更改的链表头元素
hiHead = e; else
hiTail.next = e;
hiTail = e;
}
}
while ((e = next) != null);
// 遍历结束, 将tail指向null,并把链表头放入新数组的相应下标,
形成新的映射。
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
1. 线程安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap );
2. 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;(如果你要保证线程安全的话就使用 ConcurrentHashMap );
3. 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。
4. 初始容量大小和每次扩充容量大小的不同 :
5. 创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
6. 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
7. 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
8. 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。
1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
2. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。
3、JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点):
if (fh >= 0) {
binCount = 1;
for (Node e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node pred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key, value, null);
break;
}
}
}
1. 如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的个数达到8个,则通过treeifyBin方法转化为红黑树,如果oldVal不为空,说明是一次更新操作,没有对元素个数产生影响,则直接返回旧值;
2. 如果插入的是一个新节点,则执行addCount()方法尝试更新元素个数baseCount;
面试题答案解析完整文档:【Java集合面试题【附答案解析】】
小编分享的文章到这里就结束了,整理不易,欢迎大家一起交流,喜欢小编分享的文章记得关注我点赞哟,感谢支持!