美文网首页小卜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