Java I/O模型是用于描述Java程序中数据输入输出操作、Java I/O模型提供了一组抽象类和接口,用于处理不同类型的输入、输出流和文件系统。
I/O流
流(stream)
Java的I/O基于流(stream)的概念。流是一个抽象概念,表示一组有序的数据,可以从一个地方向另一个地方传输。输入输出的介质有文件、网络、键盘、显示器等。
分类
按流向分【输入输出都是从程序角度】:输入流(Input)、输出流(Output)
按处理类型分:字节流(byte)、字符流(char)
按功能分:节点流(直接和输入输出源交互),处理流(对其他流包装的流,包装流)
流的种类繁多,但是几乎所有流都是从四个基本流继承而来
InputStream、OutputStream、Reader、Writer
一般以Stream结尾的都是字节流,以Reader/Writer结尾的都是字符流
字节流
计算机系统中,一切都是字节,因此对任何文件的操作都是可以使用一个一个字节进行操作的。字节流以字节为单位进行读写,并且通常与二进制数据打交道,如图像、音频、视频等。Java提供了两个基本的字节流类:InputStream和OutputStream。
字符流
字符流以字符为单位进行读写,并且通常与文本数据打交道,如文本文件等。Java提供了两个基本的字符流类:Reader和Writer。
I/O模型
Java I/O模型是用于描述Java程序中数据输入输出操作、Java I/O模型提供了一组抽象类和接口,用于处理不同类型的输入、输出流和文件系统。
BIO
BIO (Blocking I/O)是Java I/O模型中最常见的模型。在这个模型中,当一个线程需要读取或写入数据时,它会一直阻塞,直到所有数据都被读取或写入完成。这种方式非常简单,但是如果需要同时处理多个客户端连接时,性能将会受到很大影响。
应用程序在发出IO请求后需要一直等待系统内核完成操作,等到请求完成后线程才能执行其他操作。为了避免这种线程一直等待的情况,在数据流之间添加一个缓冲区,内核在数据准备好后先传输到缓冲区中,线程请求后不用一直等待,从缓冲区中读取数据也不用担心数据丢失。这就是接下来NIO的思想。
NIO
new io 或者non-blocking io,是Java中一种基于通道(Channel)和缓冲区(Buffer)的IO操作,它提供了与传统的流(Stream)IO 操作不同的IO处理方式。
Channel是一种抽象的数据传输通道,用于在缓冲区buffer和实体之间进行数据传输。
Channel 相当于一个双向管道,可以通过它来读取和写入数据,使用 Channel 进行数据传输时,需要先创建一个缓冲区(Buffer),然后将数据从 Channel 写入缓冲区或从缓冲区读取数据到 Channel 中。这样可以避免直接对底层系统的 I/O 操作,提高了程序的性能和可靠性。Java NIO 提供了多个 Channel 接口的实现类,包括了 FileChannel、SocketChannel、ServerSocketChannel 和 DatagramChannel 等。
NIO 的通道类似于流,但有些区别如下:
- 通道可以同时进行读写,而流只能读或者只能写
- 通道可以实现异步读写数据
- 通道可以从缓冲读数据,也可以写数据到缓冲:
NIO 的主要特点是非阻塞式的 IO 处理。在传统的 IO 模型中,当一个线程执行读写操作时,如果数据没有到达或发送完,线程会一直等待,这就是阻塞式 IO。而 NIO 允许线程在没有数据可读写时立即返回,从而释放 CPU 资源,提高系统的并发能力。
NIO 通过 Channel 和 Buffer 来分离输入和输出,数据需要先读入到缓冲区中,然后再从缓冲区写出来,这样可以灵活地控制数据的传输,这也是 NIO 的另一个优势
实现原理
在NIO中,使用Selector和Channel实现同步非阻塞。所有I/O操作都是异步的,在注册事件后可以立即返回,当事件发生时通过调用Selector的select方法来检索已经就绪的事件,并处理相应的数据。
Selector是一个高效的多路复用器,监控多个Channel的状态,并将处于就绪状态的Channel加入到就绪队列中,以提高系统吞吐量和并发性能。主线程负责创建和管理Selector以及所有的Channel,
主线程在NIO中通常是唯一的,负责创建和管理Selector以及所有的Channel,并且通过不断地调用Selector的select方法,检测Channel是否有可读或可写的数据。当数据准备好时,主线程将其传递给子线程进行处理,然后回到主线程,继续监听其他Channel的状态变化。由于Channel的读写操作是非阻塞的,因此主线程永远不会被阻塞,可以充分利用CPU资源。
另外,NIO 还引入了缓冲区 Buffer 的概念,数据需要先读入到缓冲区中,然后再从缓冲区写出来,这样可以灵活地控制数据的传输。此外,由于缓冲区对数据进行了封装,在数据传输过程中可以进行加解密、压缩等操作,提高了数据的安全性和可靠性。
下面这个基于Java NIO的样例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NioClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("example.com", 80));
String message = "Hello, server!";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(message.getBytes());
buffer.flip();
while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
buffer.clear();
int bytesRead = socketChannel.read(buffer);
while (bytesRead != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
bytesRead = socketChannel.read(buffer);
}
socketChannel.close();
}
}这个样例演示了如何使用NIO的SocketChannel来连接到一个远程服务器,并向其发送一条消息。首先,我们创建了一个SocketChannel对象,并使用它的connect()方法来连接到指定的服务器地址和端口。接着,我们将要发送的消息转换成ByteBuffer,并使用SocketChannel的write()方法将其发送给服务器。然后,我们使用同样的SocketChannel对象读取服务器响应,将其读入ByteBuffer中,并将其打印到控制台上。
这个样例还演示了如何在NIO中使用ByteBuffer对象来处理输入输出数据。当我们需要读取数据时,我们首先调用flip()方法来准备缓冲区进行读操作。然后,我们可以通过调用get()方法逐个读取数据,并对其进行相应的处理。最后,我们使用clear()方法来清空缓冲区,以便可以再次使用它来读写数据。
AIO
AIO (Asynchronous I/O)是Java I/O模型的另一种改进版异步型IO。主要用于处理网络通信和文件I/O操作,在这个模型中,I/O操作不再由程序自己完成,而是由操作系统完成。当一个I/O操作完成后,操作系统会通知应用程序,以便应用程序可以执行下一步操作。相比于NIO,AIO可以处理更多的连接,并且具有更好的可伸缩性。但是,AIO使用起来更复杂,对于一些简单的应用程序可能过于复杂。
在AIO模型中,当一个I/O操作完成后,会通过回调函数的方式来通知应用程序,而不需要等待I/O操作的结果返回。这样可以避免线程的阻塞,提高了系统的并发性能。
NIO是半同步半异步的I/O模型,而AIO则是纯异步的I/O模型。在NIO中,读写操作仍然会阻塞线程,只有就绪事件才是异步处理的;而在AIO中,读写操作不会阻塞线程,通过回调函数的方式来实现异步处理。