面试篇:集合

一、java集合框架体系 

面试篇:集合_第1张图片

二、 Java中有哪些容器(集合类)

面试篇:集合_第2张图片

  • Set代表无序的,元素不可重复的集合;

  • List代表有序的,元素可以重复的集合;

  • Queue代表先进先出(FIFO)的队列;

  • Map代表具有映射关系(key-value)的集合。

这些接口拥有众多的实现类,其中最常用的实现类有HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap等。 

三、集合底层详解

1、List接口

面试篇:集合_第3张图片

2、Set接口面试篇:集合_第4张图片

3、Queue接口

  • Deque:它代表一个双端队列,接口里定义了一些双端队列的方法,这些方法允许从两端来操作队列的元素。
    • ArrayDeque:是基于数组实现的双端队列,采用动态可重分配的Object[]数组来存储元素,可以指定Object[]数组长度,默认为16
  • PriorityQueue:实现类保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序,因此当调用peek()或poll()方法取出队列中的元素时,并不是取出最先进入队列的元素,而是取出队列中最小的元素。注意:因为PriorityQueue是进行排序的,所以不允许插入的元素为null

4、Map接口

面试篇:集合_第5张图片

四、CopyOnWrite解析

1、核心思想

读写分离,空间换时间,避免为保证并发安全导致的激烈的锁竞争。

2、关键点

  • CopyOnWrite适用于读多写少的情况,最大程度的提高读的效率;
  • CopyOnWrite是最终一致性,在写的过程中,原有的读的数据是不会发生更新的,只有新的读才能读到最新数据;
  • 如何使其他线程能够及时读到新的数据,需要使用volatile变量;
  • 写的时候不能并发写,需要对写操作进行加锁;

3、底层原理

        CopyOnWriteArrayList 相对于 ArrayList 线程安全,底层通过复制数组的方式来实现,其核心概念就是: 数据读取时直接读取,不需要锁,数据写入时,需要锁,且对副本进行操作。那么当数据的操作以读取为主时,我们便可以省去大量的读锁带来的消耗。

        为了能让多线程操作List时,一个线程的修改能被另一个线程立马发现,CopyOnWriteList采用了Volatile关键词来进行修饰,即每次数据读取不从缓存里面读取,而是直接从数据的内存地址中读取。 只要把最新的线程对它写操作,其他线程立马可以看到最新的数据

五、阻塞队列

        当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。

1、ArrayBlockingQueue

        有界阻塞队列,底层采用数组实现。ArrayBlockingQueue 一旦创建,容量不能改变。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不能保证线程访问队列的公平性,参数fair可用于设置线程是否公平访问队列。为了保证公平性,通常会降低吞吐量

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ArrayBlockingQueueExample {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new ArrayBlockingQueue<>(10);

        for (int i = 0; i < 10; i++) {
            queue.put(i);
        }

        System.out.println("队列已满,添加元素会阻塞");

        for (int i = 0; i < 5; i++) {
            System.out.println(queue.take());
        }

        System.out.println("队列已空,取元素会阻塞");

        for (int i = 0; i < 10; i++) {
            queue.put(i);
        }
    }
}

        这个例子中,我们创建了一个大小为10的ArrayBlockingQueue,并向其中添加了10个元素。当队列已满时,添加元素会阻塞。当队列为空时,取元素会阻塞 

2、LinkedBlockingQueue

        LinkedBlockingQueue是一个用单向链表实现的有界阻塞队列,可以当做无界队列也可以当做有界队列来使用。通常在创建 LinkedBlockingQueue 对象时,会指定队列最大的容量。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。与 ArrayBlockingQueue 相比起来具有更高的吞吐量。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class LinkedBlockingQueueExample {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new LinkedBlockingQueue<>(10);

        for (int i = 0; i < 10; i++) {
            queue.put(i);
        }

        System.out.println("队列已满,添加元素会阻塞");

        for (int i = 0; i < 5; i++) {
            System.out.println(queue.take());
        }

        System.out.println("队列已空,取元素会阻塞");

        for (int i = 0; i < 10; i++) {
            queue.put(i);
        }
    }
}

这个例子中,我们创建了一个默认大小的LinkedBlockingQueue,并向其中添加了10个元素。当队列已满时,添加元素会阻塞。当队列为空时,取元素会阻塞 。 

3、PriorityBlockingQueue

        支持优先级的无界阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来进行排序。

        PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,如果空间不够的话会自动扩容

        PriorityQueue 的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的(comparable),否则报 ClassCastException 异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。

public class Test2 {
    public static void main(String args[]) throws InterruptedException {
        BlockingQueue queue = new PriorityBlockingQueue<>();
        queue.put(3);
        queue.put(4);
        queue.put(2);
        queue.put(1);
        queue.put(0);
        for (int i = 0; i < 5; i++) {
            System.out.println(queue.take());
        }
    }
}

运行结果: 

面试篇:集合_第6张图片 

4、DelayQueue

        支持延时获取元素的无界阻塞队列。队列使用PriorityBlockingQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。

public class Test2 {
    public static void main(String args[]) throws InterruptedException {
        BlockingQueue queue = new DelayQueue<>();

        for (int i = 0; i < 10; i++) {
            queue.put(new DelayedElement(i, 1000));
        }

        System.out.println("队列已满,添加元素会阻塞");

        for (int i = 0; i < 5; i++) {
            System.out.println(queue.take());
        }

        System.out.println("队列已空,取元素会阻塞");

        for (int i = 0; i < 10; i++) {
            queue.put(new DelayedElement(i, 1000));
        }
    }
    static class DelayedElement implements Delayed {
        private int value;
        private long delayTime;

        public DelayedElement(int value, long delayTime) {
            this.value = value;
            this.delayTime = System.currentTimeMillis() + delayTime;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
        }

        @Override
        public String toString() {
            return "DelayedElement{" +
                    "value=" + value +
                    ", delayTime=" + delayTime +
                    '}';
        }
    }
}

运行结果:

面试篇:集合_第7张图片 

 

5、SynchronousQueue

        不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素。支持公平访问队列。

        SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身不存储任何元素,非常适合传递性场景。SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。

    public static void main(String args[]) throws InterruptedException {
        BlockingQueue queue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                System.out.println("线程1开始放入元素");
                queue.put(1);
                System.out.println("线程1放入元素成功");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                System.out.println("线程2开始取出元素");
                Integer element = queue.take();
                System.out.println("线程2取出元素成功:" + element);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

运行结果:

面试篇:集合_第8张图片 

6、LinkedTransferQueue 

        由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,多了tryTransfer和transfer方法。

        transfer方法:如果当前有消费者正在等待接收元素(take或者待时间限制的poll方法),transfer可以把生产者传入的元素立刻传给消费者。如果没有消费者等待接收元素,则将元素放在队列的tail节点,并等到该元素被消费者消费了才返回。

        tryTransfer方法:用来试探生产者传入的元素能否直接传给消费者。如果没有消费者在等待,则返回false。和上述方法的区别是该方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedTransferQueue;

public class LinkedTransferQueueExample {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new LinkedTransferQueue<>();

        new Thread(() -> {
            try {
                System.out.println("线程1开始放入元素");
                queue.transfer(1);
                System.out.println("线程1放入元素成功");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                System.out.println("线程2开始取出元素");
                Integer element = queue.take();
                System.out.println("线程2取出元素成功:" + element);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

运行结果:

线程1开始放入元素
线程2开始取出元素
线程1放入元素成功
线程2取出元素成功:1

你可能感兴趣的:(面试,面试,职场和发展)