JAVA面试汇总-集合部分,计划四篇,这是最后一篇。标题我就按照上一篇的继续,这样每次一部分总结完了,可以直接串起来来个长篇的。
16.Java集合的快速失败机制 “fail-fast”?
(1)fail-fast是一种错误机制,实际上是针对集合类使用过程中,如果被修改、删除其中的内容后触发的一种错误机制。
(2)具体可以看下面这个例子,就是当操作了remove后,ArrayList中有一个modCount,这个值每次增加修改删除都会增加数值,而创建iterator时候,会将ArrayList的modCount赋值给iterator的expectedModCount,每次操作iterator(hasNext不会判断)都会判断这两个值是否相同,不同则直接抛出ConcurrentModificationException
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(10);
arrayList.add(11);
arrayList.add(12);
Iterator<Integer> iterator = arrayList.iterator();
Integer last = 0;
while (iterator.hasNext()) {
Integer next = iterator.next();
System.out.println(next);
// 神奇的现象,改成11就不会报错,为什么呢?
// 因为remove后,上面的iterator.hasNext()就是false了
if (next == 12) {
arrayList.remove(next);
}
}
}
}
//输出
10
11
12
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at cn.homecredit.miniapp.bff.application.command.ArrayListTest.main(ArrayListTest.java:15)
(3)可以再看下抛出ConcurrentModificationException的代码,如果操作过程中被修改了就会抛出异常中断操作。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
17.fail-fast 与 fail-safe 之间的区别?
(1)fail-fast是上面20条讲的一种错误机制,当iterator循环集合过程中,被修改了就会抛出错误。
(2)fail-safe是任何对集合结构的修改都会在一个复制的集合上进行修改,不会抛出ConcurrentModificationException,concurrent包下的都是fail-safe机制的
(3)大家来看一下CopyOnWriteArrayList的测试情况,不会抛出异常
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
public class ArrayListTest {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> arrayList = new CopyOnWriteArrayList<>();
arrayList.add(10);
arrayList.add(11);
arrayList.add(12);
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
System.out.println(next);
if (next == 10) {
arrayList.remove(next);
}
}
}
}
//输出
10
11
12
(3)看一下CopyOnWriteArrayList.add的源码,加锁,先copy出来一个,再到copy的上面操作,解锁,有一个重点,setArray方法把旧的数组废弃掉
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
(4)CopyOnWriteArrayList.remove的源码,更有意思,我只看重点部分了,加锁,创建一个size-1的新数组,从需要remove的节点的前后分别copy到新数组上,解锁
Object[] newElements = new Object[len - 1];
System.arraycopy(current, 0, newElements, 0, index);
System.arraycopy(current, index + 1,
newElements, index,
len - index - 1);
setArray(newElements);
18.Iterator类有什么作用
(1)对集合进行迭代的迭代器,将内容从集合中循环读取出来
(2)有人说iterator效率比较高,比foreach效率高咱们来试试,我的结论不是这么回事。。。
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList();
for (int i = 0; i < 10000000; i++) {
arrayList.add(i);
}
Long start = System.currentTimeMillis();
testForEach(arrayList);
Long end = System.currentTimeMillis();
System.out.println((end-start) + " ms");
Long newstart = System.currentTimeMillis();
testIterator(arrayList);
Long newend = System.currentTimeMillis();
System.out.println((newend-newstart) + " ms");
}
public static void testIterator(ArrayList<Integer> arrayList){
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
}
}
public static void testForEach(ArrayList<Integer> arrayList){
for (Integer i:arrayList) {
}
}
}
//输出
38 ms
35 ms
//如果修改testForEach和testIterator的执行顺序再来看看输出结果,谁先执行谁慢3-5ms,我的结论是量效率差不多
40 ms
35 ms
19.Queue中的poll()方法和remove()方法区别?
(1)poll和remove都是取出头元素,poll如果没有取到值会返回null,但是remove这个时候会报异常
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class QueueTest {
public static void main(String[] args) {
BlockingQueue<String> bq = new ArrayBlockingQueue<String>(10);
bq.add("111");
bq.add("222");
//分别取出111,222
System.out.println(bq.remove());
System.out.println(bq.poll());
//元素取出来了,里面没有了,可以看看poll和remove的返回
System.out.println(bq.poll());
System.out.println(bq.remove());
}
}
//输出
111
222
null
Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at cn.homecredit.miniapp.bff.application.command.ArrayListTest.main(ArrayListTest.java:16)
(2)其实还可以引申peek()和element()的区别,peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,调用element()方法会抛出NoSuchElementException异常。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class QueueTest {
public static void main(String[] args) {
BlockingQueue<String> bq = new ArrayBlockingQueue<String>(10);
bq.add("111");
bq.add("222");
//一直取出的都是111,因为没有移除队头元素
System.out.println(bq.element());
System.out.println(bq.peek());
//取出元素
System.out.println(bq.poll());
System.out.println(bq.poll());
//元素取出来了,里面没有了,可以看看peek和element的返回
System.out.println(bq.peek());
System.out.println(bq.element());
}
}
//输出
111
111
111
222
null
Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractQueue.element(AbstractQueue.java:136)
at cn.homecredit.miniapp.bff.application.command.QueueTest.main(QueueTest.java:19)
20.JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
(1)浪费内存空间,每个线程都使用独立的分段的Map
(2)实际使用过程中,发生冲突抢占分段锁的情况不多,另外加了分段锁导致了存入值的操作效率变低
(3)让我来设计?那就按照1.8的cas来呗。。。
(4)每次来修改,通过hash计算出要修改的数组位置后,将这个数组位置加锁,然后到这个数据的链表上修改,仍然是循环查找到next为null的,把新增的节点设置为next,如果这个链表上的个数超过8个,注意了,超过8个只是扩容(妈蛋,网上帖子都是8个就扩容,骗人的玩意儿),不是转变为红黑树,只有当超过64个时候才转换为红黑树,修改完毕后,释放锁。
网友评论