在上篇《Java IO(2)阻塞式输入输出(BIO)》的末尾谈到了什么是阻塞式输入输出,通过Socket编程对其有了大致了解。现在再重新回顾梳理一下,对于只有一个“客户端”和一个“服务器端”来讲,服务器端需要阻塞式接收客户端的请求,这里的阻塞式表示服务器端的应用代码会被挂起直到客户端有请求过来,在高并发的应用场景有多个客户端发起连接下非阻塞式IO(NIO)是不二之选(且只需要在服务器端使用1个线程来管理,并不需要多个线程来处理多个连接)。在现实情况下,Tomcat、Jetty等很多Web服务器均使用了NIO技术。
接下来对于非阻塞式输入输出(NIO)的学习以及理解首先从它的三个基础概念讲起。
Channel(通道)
在NIO中,你需要忘掉“流”这个概念,取而代之的是“通道”。举例在网络应用程序中有多个客户端连接,此时数据传输的概念并不是“流”而“通道”,通道与流最大的不同就是,通道是双向的,而流是单向的(例如InputStream、OutputStream)。
Buffer(缓冲区)
在NIO中并不是简单的将流的概念替换为了通道,与通道搭配的是缓冲区。在BIO的字节流中并不会使用到缓冲区,而是直接操作文件通过字节方式直接读取,而NIO则不同,它会将通道中的数据读入缓存区,或者将缓存区的数据写入通道。
Selector(选择器)
如果使用NIO的应用程序中只有一个Channel,选择器则是可以不需要的,而如果有多个Channel,换言之有多个连接时,此时通过选择器,在服务器端的应用程序中就只需要1个线程对多个连接进行管理。
当然从最开始就说到Channel是双向的,所以在最终图的示例为下图所示:
下面再重新回到这三个概念,详细解释它们是如何协同工作的。
Channel & Buffer
通常情况下Channel会和Buffer配合使用,但可以不使用Channel。首先需要明确的是,应用程序不管是从文件(包括网络或者其他什么地方)中读取数据,还是写入数据到文件(包括网络或者其他什么地方)都需要Buffer。
1. 直接将数据写入Buffer,应用程序从Buffer中获取数据
1 ByteBuffer buffer = ByteBuffer.allocate(1024);2 byte b = 121;3 buffer.put(b);4 buffer.flip(); //读写转换,由“写模式”转换为“读模式”
5 System.out.println((char)buffer.get());
第1行,分配一个1KB大小的Buffer缓冲区,ByteBuffer.allcoate返回HeapByteBuffer实例。
第3行,向Buffer中写入一个字节。
第4行,Buffer由“写模式”转换为“读模式”。
第5行,ByteBuffer.get方法读取Buffer中的数据,并且position索引+1。 在上面的代码中有一个重点——flip方法,这个方法的存在是由于Buffer兼顾了读和写的操作,在ByteBuffer的实现中有三个重要的成员变量需要注意: capacity——Buffer容量 position——索引位置 limit——读时表示最大容量,即limit = capacity;写时表示最后一个数据所在的索引位置。 用图例来说明上面代码的执行过程。