Java基础技术-Java其他主题【面试】
Java基础技术IO与队列
Java BIO、NIO、AIO
Java 中 BIO、NIO、AIO 的区别是什么?
含义不同:
BIO(Blocking IO)是同步并阻塞的 IO,线程发起 IO 请求后,不论内核是否准备好 IO 操作,都会一直阻塞直到操作完成
NIO(Non-blocking IO)是同步非阻塞的 IO,线程发起 IO 请求后立即返回;内核在做好 IO 操作的准备之后,通过调用注册的回调函数通知线程做 IO 操作,线程开始阻塞,直到操作完成
AIO(Asynchronous IO)是异步非阻塞的 IO,线程发起 IO 请求,立即返回;内存做好 IO 操作的准备之后,做 IO 操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做 IO 操作完成或者失败
应用场景不同:
BIO 从 JDK1.4 之前的版本,适用于低负载、低并发、业务逻辑耗时较长的场景
NIO 从 JDK1.4 开始支持,适用于高负载高并发且业务逻辑简单(轻操作)的场景,典型场景是聊天服务器
AIO 从 JDK1.7 开始支持,适用于高负载高并发且业务逻辑复杂(重操作)的场景,典型场景是相册服务器
Java 中三者的关系
从发展历史来看,BIO->NIO->AIO,后者的出现是为了扩展前者的应用场景
BIO:在服务端,通常是在 while 循环中调用 accept 方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成
如果 BIO 要能够同时处理多个客户端请求,就必须使用多线程,即每次 accept 阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续 accept 等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理
NIO:与 BIO 的最大的区别是多路复用的思想,只需要开启一个线程就(或者少量多线程)可以处理来自多个客户端的 IO 事件,用来扩展 BIO 的高并发场景
若服务端监听到客户端连接请求,便为其建立通信套接字 (java 中就是通道),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通信套接字
若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理
AIO:与 NIO 不同,当进行读写操作时,只须直接调用 API 的 read 或 write 方法即可,这两种方法均为异步的,完成后会主动调用回调函数
如果是读操作,操作系统会将可读的流传入 read 方法的缓冲区,并通知应用程序,如果是写操作,操作系统在将 write 方法传递的流写入完成后,也会通知应用程序
在 JDK1.7 中,在 channels 包下增加了四个异步通道来实现:AsynchronousSocketChannel,AsynchronousServerSocketChannel,AsynchronousFileChannel,AsynchronousDatagramChannel,这四个类的 read/write 方法,都会返回一个带回调函数的对象,当执行完读取 / 写入操作后,直接调用回调函数
同步与异步、阻塞与非阻塞的区别是什么?
同步与异步:
同步:调用线程发出同步请求后,在没有得到结果前,该调用就不会返回,前面的同步调用处理完了后才能处理下一个同步调用
异步:调用线程发出异步请求后,在没有得到结果前,该调用就返回了,真正的结果数据会在业务处理完成后以信号或者回调的形式通知调用者
阻塞与非阻塞:
阻塞:调用线程发出请求后,在没有得到结果前,该线程就会被挂起,此时该线程处于非可执行状态,直到返回结果返回后,此线程才会被唤醒,继续运行
非阻塞:调用线程发出请求后,在没有得到结果前,该调用就返回了,整个过程该线程不会被挂起
简单地说:同步和异步的关键差异在于 IO 操作就绪后有没有回调,是针对数据处理端而言,而在同步的时候 ,IO 操作是否就绪后没有回调,通常需要业务发起端使用轮询方法查看,如果是异步的话 ,那IO 操作是否就绪后有回调,通常会主动唤起业务发起端
阻塞和非阻塞的关键差异在于 IO 操作尚未就绪时,进程是否需要等待,是针对业务发起端而言,在阻塞的情况下,要是IO 操作沿未就绪时,业务发起端的用户程序就进入等待状态,直到数据准备好或者可以读写为止,而在非阻塞的情况时,如果 IO 操作沿未就绪时,那业务发起端的用户程序可以立刻返回无须等待
比如说,我煮米饭,有一个普通的蒸锅,还有一个电饭煲,我拿着蒸锅去炉灶上蒸米饭,然后在边上等米饭出锅,这就是同步阻塞
我要是把蒸锅放在炉灶上蒸米饭,然后不在边儿上等着,我去打五把CSGO,中间时不时过来看看能不能出锅,这就是同步非阻塞
这次换个锅,用电饭煲,一样的,等着米饭出锅,不过电饭煲会在米饭熟的时候自己叮一下,这次我还是在边儿上等着出锅,这就是异步阻塞
然后下一次我觉得我在边上干等着,有点儿没意思,就做好准备以后,去打五把CSGO去了,电饭煲不叮我就不过来看,这就是异步非阻塞
对于炊具来说,蒸锅就是同步,而电饭煲就是异步的,蒸锅只能让我(调用者)来轮询能不能出锅,而电饭煲是饭熟了就回自己响,提示我饭熟了,至于阻塞非阻塞来说,我在边上待着不动就是阻塞,放着不管去打CSGO就是非阻塞
字节流、字符流的区别及适用场景分别是什么?
区别:
处理单元不同,J 字节流处理的最基本单位为 1 个字节,字符流处理的最基本的单元是 Unicode 代码单元(大小 2 字节)
字节流默认不使用缓冲区;字符流使用缓冲区
适用场景:
字节流实际上可以处理任何文件,因为字节是存储的基础单元,而待处理的流如果是可打印的字符,那么用字符流更方便一些
什么是缓冲区?有什么作用呢?
可以把缓冲区理解为一段特殊的内存,字符流操作时为了不那么频繁地读写 IO,会把一部分数据暂时读入到内存的某块区域,然后直接从该区域读取数据,以提升程序的性能
在字符流的操作中,所有的字符都是在内存中形成的,从而使用缓冲区暂存数据,如果想在不关闭时也可以将字符流的内容全部输出,则可以使用 Writer 类中的 flush () 方法完成
栈(Stack)和队列(Queue)的异同?
相同点:
栈和队列都是属于线性表
栈和队列插入操作都是限定在线性表的头尾进行
栈和队列插入与删除的时间复杂度都是 O (1)
不同点:
特性不同,栈后进先出(LIFO,Last In First Out),队列先进先出(FIFO,First In First Out)
栈只在表的一端进行插入和删除操作,队列只在表的一端进行插入操作,在表的另一端进行删除操作
JAVA 中的栈 (Stack) 继承自 Vector,再往上的接口是 List/Collection,而队列(Queue) 直接继承的是 Collection 接口
如何用两个栈实现队列(入队和出队)?
代码如下:
public class StackQueue<E> {
private Stack<E> stack1 =new Stack<>();
private Stack<E> stack2 =new Stack<>();
public void push(E element) {
stack1.add(element);
}
public E poll() {
if (stack2.isEmpty()) {
while (stack1.size() > 0) {
stack2.add(stack1.pop());
}
}
if (stack2.isEmpty()) {
throw new RuntimeException("queue is Empty!");
}
E head = stack2.pop();
return head;
}
public static void main(String[] args) {
StackQueue<String> stackQueue = new StackQueue();
stackQueue.push("first");
stackQueue.push("second");
}
}
实现过程简单地说,就是把数据先压入 stack1,然后再从 stack1 中取出压入 stack2(后进先出),取数的时候直接从 stack2 出(后进先出),经过两次后进先出就符合队列先进先出的特性了,其中stack1 用于存储元素,stack2 用于弹出元素
ArrayBlockingQueue 和 LinkedBlockingQueue 的区别是什么?
ArrayBlockingQueue 和 LinkedBlockingQueue 都是阻塞队列(BlockingQueue)
区别:
内部存储结构不同,ArrayBlockingQueue 采用的是数组存储,而 LinkedBlockingQueue 采用的是 Node 节点
ArrayBlockingQueue 初始化时必须指定容量值,LinkedBlockingQueue 可以不用指定(默认容量为 Integer.MAX_VALUE)
Queue 及其常用子类可分为几类?
大致可分为三类:
双端队列(Deque):头部和尾部都支持元素插入和获取
阻塞队列(BlockingQueue):在元素的添加 / 删除操作时,如果没有成功,会阻塞等待执行,例如,当添加元素时,如果队列元素已满,队列会阻塞等待直到有空位时再插入
非阻塞队列(Non BlockingQueue):在元素的添加 / 删除操作时,如果没有成功,会直接返回操作的结果(通常双端队列也属于非阻塞队列)
Queue 的常见特性PriorityBlockingQueue,DelayQueue,SynchronousQueue?
PriorityBlockingQueue:带优先级的无界阻塞队列,元素按优先级顺序被移除,而不是先进先出队列(FIFO)
此外,队列中的元素要具有比较能力(用于判定优先级),PriorityBlockingQueue 不允许 null 元素
DelayQueue:存放 Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素
该队列的头部是延迟期满后保存时间最长的 Delayed 元素,如果延迟都还没有期满,则队列没有头部,DelayQueue 不允许使用 null 元素
SynchronousQueue:同步阻塞队列,它的每个插入操作都要等待其他线程相应的移除操作,反之亦然
它的一个典型应用场景是 Executors.newCachedThreadPool () ,在新任务到来时创建新的线程,如果有空闲线程则会重复使用,SynchronousQueue 不允许 null 元素