美文网首页小卜java
JAVA面试汇总(三)集合(四)

JAVA面试汇总(三)集合(四)

作者: 汤太咸啊 | 来源:发表于2022-01-05 23:33 被阅读0次

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个时候才转换为红黑树,修改完毕后,释放锁。

感谢各位的阅读,帮忙点赞,感谢各位。

相关文章

网友评论

    本文标题:JAVA面试汇总(三)集合(四)

    本文链接:https://www.haomeiwen.com/subject/soclcrtx.html