马上到今年的金三银四了,又是跳槽的好季节,准备跳槽的同学都摩拳擦掌准备大面好几场,本次小编为大家准备了精选的 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.51cto.com/14994509/2692669 |
2 | 微服务 | Java微服务面试题(2021最新版) | https://blog.51cto.com/u_14994509/2694841 |
3 | 并发编程 | Java并发编程面试题(2021最新版) | https://blog.51cto.com/u_14994509/2703303 |
4 | Java基础 | Java基础知识面试题(2021最新版) | https://blog.51cto.com/blogger/success/2706687 |
5 | Spring Boot | Spring Boot面试题(2021最新版) | https://blog.51cto.com/u_14994509/2709421 |
6 | Redis | Redis面试题(2021最新版) | https://blog.51cto.com/u_14994509/2711546 |
7 | Spring MVC | Spring MVC面试题(2021最新版) | https://blog.51cto.com/u_14994509/2711562 |
8 | Spring Cloud | Spring Cloud面试题(2021最新版) | https://blog.51cto.com/u_14994509/2716500 |
9 | MySQL优化 | MySQL优化面试题(2021最新版) | https://blog.51cto.com/u_14994509/2716504 |
10 | JVM | JVM性能调优面试题(2021最新版) | https://blog.51cto.com/u_14994509/2716514 |
11 | Linux | Linux面试题(2021最新版) | https://blog.51cto.com/u_14994509/2718559 |
12 | Mybatis | Mybatis面试题(2021最新版) | https://blog.51cto.com/u_14994509/2718588 |
13 | 网络编程 | TCP,UDP,Socket,Http网络编程面试题(2021最新版) | https://blog.51cto.com/u_14994509/2718599 |
14 | 设计模式 | 设计模式面试题(2021最新版) | https://blog.51cto.com/u_14994509/2735448 |
15 | 大数据 | 大数据面试题100道(2021最新版) | https://blog.51cto.com/u_14994509/2736601 |
16 | Tomcat | Tomcat面试题(2021最新版) | https://blog.51cto.com/u_14994509/2739386 |
17 | 多线程 | 多线程面试题(2021最新版) | https://blog.51cto.com/u_14994509/2739435 |
18 | Nginx | Nginx_BIO_NIO_AIO面试题(2021最新版) | https://blog.51cto.com/u_14994509/2742718 |
19 | memcache | memcache面试题(2021最新版) | https://blog.51cto.com/u_14994509/2743980 |
20 | java异常 | java异常面试题(2021最新版) | https://blog.51cto.com/u_14994509/2746759 |
21 | Java虚拟机 | Java虚拟机面试题(2021最新版) | https://blog.51cto.com/u_14994509/2747596 |
22 | Java集合 | Java集合面试题(2021最新版) | 持续更新中! |
23 | Git常用命令 | Git常用命令(2021最新版) | 持续更新中! |
24 | Elasticsearch | Elasticsearch面试题(2021最新版) | 持续更新中! |
25 | Dubbo | Dubbo面试题(2021最新版) | 持续更新中! |
集合的特点主要有如下两点:
Map接口和Collection接口是所有集合框架的父接口:
List<String> list = new ArrayList<>(); list. add("x"); Collection<String> clist = Collections. unmodifiableCollection(list); clist. add("y"); // 运行时此行报错 System. out. println(list. size());
二、Collection接口
Iterator 使用代码如下:
List<String> list = new ArrayList<>(); Iterator<String> it = list. iterator(); while(it. hasNext()){ String obj = it. next(); System. out. println(obj); }
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModifificationException 异常。
// list to array List<String> list = new ArrayList<String>(); list.add("123"); list.add("456"); list.toArray(); // array to list String[] array = new String[]{"123","456"}; Arrays.asList(array);
List<String> 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<E> extends AbstractList<E> implements List<E>, 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<size; i++) s.writeObject(elementData[i]); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); }
private static final Object PRESENT = new Object(); private transient HashMap<E,Object> 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<K,V>[] tab; Node<K,V> 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<K,V> 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<K,V>)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; }
final Node<K,V>[] resize() { Node<K,V>[] 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<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //新建hash桶数组 table = newTab; //将新数组的值复制给旧的hash桶数组 // 如果原先的数组没有初始化,那么resize的初始化工作到此结束,否则进入扩容元素重排逻辑,使 其均匀的分散 if (oldTab != null) { // 遍历新数组的所有桶下标 for (int j = 0; j < oldCap; ++j) { Node<K,V> 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<K,V>)e).split(this, newTab, j, oldCap); // e是链表的头并且e.next!=null,那么处理链表中元素重排 else { // preserve order // loHead,loTail 代表扩容后不用变换下标,见注1 Node<K,V> loHead = null, loTail = null; // hiHead,hiTail 代表扩容后变换下标,见注1 Node<K,V> hiHead = null, hiTail = null; Node<K,V> 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; }
可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点:
3、JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点):
if (fh >= 0) { binCount = 1; for (Node<K,V> 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<K,V> pred = e; if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } }