NIO是一种同步非阻塞的I/O模型,在Java 1.4中引入了NIO框架,对应java.nio
包,提供了channel、selector、buffer等。
NIO中的N可以理解为Non-blocking不在单纯是New,它支持面向缓冲的,基于通道的I/O操作方法。NIO提供了与传统BIO模型中的Socket
和ServerSocket
相对应的SocketChannel
和ServerSocketChannel
,两种不同的套接字通道实现两种通道都支持阻塞和非阻塞式的两种模式,阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性不好;非阻塞模式刚好相反,对于低负载的、低并发的应用程序,可以使用同步阻塞I/O来提高开发效率和维护性;对于高负载的、高并发的应用,应该使用NIO的非阻塞模式来开发。
NIO
NIO(Non-blocking I/O)是Java中的一种非阻塞式IO模型,它基于缓冲区(Buffer)和通道(Channel)的概念,提供了更高效的IO操作和更好的并发处理能力。NIO模型使用单个线程处理多个IO操作,通过选择器(Selector)来管理多个通道,实现非阻塞的IO操作。
NIO的相关概念和使用方式:
-
通道(Channel):通道是NIO模型中的基本组件,用于读取和写入数据。通道提供了高效的数据传输能力,并且可以通过非阻塞方式进行IO操作。常见的通道类有FileChannel、SocketChannel和ServerSocketChannel等。
-
缓冲区(Buffer):缓冲区是NIO模型中用于存储数据的对象。数据从通道读取到缓冲区,或从缓冲区写入到通道。缓冲区提供了对数据的高效访问和操作。常见的缓冲区类有ByteBuffer、CharBuffer和ByteBuffer等。
-
选择器(Selector):选择器是NIO模型中的关键组件,用于管理多个通道的IO操作。通过选择器,可以将一个线程管理多个通道,实现非阻塞的IO操作。选择器会不断地轮询注册在其上的通道,发现有IO事件发生时进行处理。
-
使用方式:
- 创建通道:通过Channel类的工厂方法创建通道对象,如FileChannel.open()、SocketChannel.open()等。
- 创建缓冲区:通过Buffer类的静态方法创建缓冲区对象,如ByteBuffer.allocate()、CharBuffer.allocate()等。
- 注册通道:将通道注册到选择器上,通过SelectableChannel.register()方法实现。
- 选择就绪通道:通过选择器的select()方法选择就绪的通道,进行相应的IO操作。
- 处理IO事件:根据不同的IO事件类型,进行读取、写入或其他操作。
- 关闭通道:在完成IO操作后,及时关闭通道,释放资源。
BIO模式的操作
BIO 以流的方式处理数据,面向流的 BIO 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。
NIO模式的操作
NIO 以块的方式处理数据,面向块的 NIO 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 NIO 缺少一些面向流的 BIO 所具有的优雅性和简单性。
解决的问题:
NIO可以用来解决传统的阻塞IO模型中的一些问题,包括:
- 高并发处理能力:NIO模型使用单个线程处理多个IO操作,通过选择器(Selector)管理多个通道,实现非阻塞的IO操作。这样可以提高系统的并发处理能力,减少线程数量和资源消耗。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class HighConcurrencyServer {
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
clientChannel.read(buffer);
buffer.flip();
//TODO处理读取到的数据
// ...
clientChannel.close();
}
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 资源利用率提升:由于NIO模型使用单个线程处理多个IO操作,可以更有效地利用系统资源,避免了为每个连接创建一个独立线程的资源浪费。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class ResourceUtilizationServer {
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
clientChannel.read(buffer);
buffer.flip();
//TODO处理读取到的数据
// ...
clientChannel.close();
}
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 非阻塞IO操作:NIO模型中的IO操作是非阻塞的,即当没有数据可读取时,IO操作不会被阻塞,而是立即返回。这样可以避免线程被长时间阻塞,提高系统的响应性能。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class NonBlockingIOExample {
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("example.com", 80));
while (!socketChannel.finishConnect()) {
//TODO等待连接完成
//....
}
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello, Server".getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
socketChannel.read(buffer);
buffer.flip();
//TODO处理读取到的数据
// ...
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 选择器机制:NIO模型通过选择器(Selector)来管理多个通道的IO操作。选择器会不断地轮询注册在其上的通道,发现有IO事件发生时进行处理,避免了传统阻塞式IO模型中需要为每个通道创建一个线程的开销。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class SelectorExample {
public static void main(String[] args) {
try {
Selector selector = Selector.open();
SocketChannel channel1 = SocketChannel.open();
channel1.configureBlocking(false);
channel1.connect(new InetSocketAddress("example.com", 80));
channel1.register(selector, SelectionKey.OP_CONNECT);
SocketChannel channel2 = SocketChannel.open();
channel2.configureBlocking(false);
channel2.connect(new InetSocketAddress("example.org", 80));
channel2.register(selector, SelectionKey.OP_CONNECT);
while (true) {
selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key.channel();
if (channel.isConnectionPending()) {
channel.finishConnect();
}
//TODO 处理连接完成事件
// ...
}
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 灵活的缓冲区操作:NIO模型使用缓冲区(Buffer)来读取和写入数据,提供了灵活的数据操作方式。可以直接操作缓冲区来读取、写入数据,而无需通过输入流和输出流的方式。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class BufferExample {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
FileChannel inputChannel = fis.getChannel();
FileChannel outputChannel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (inputChannel.read(buffer) != -1) {
buffer.flip();
outputChannel.write(buffer);
buffer.clear();
}
inputChannel.close();
outputChannel.close();
fis.close();
fos.close();
System.out.println("文件复制成功.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO和BIO
区别
-
阻塞 vs 非阻塞:BIO模型是阻塞式IO模型,即当没有数据可读取时,IO操作会被阻塞,直到有数据可读取。而NIO模型是非阻塞式IO模型,当没有数据可读取时,IO操作不会被阻塞,而是立即返回。
-
线程模型:BIO模型使用一个线程处理一个连接,当有多个连接时,需要创建多个线程,可能导致资源浪费和性能下降。而NIO模型使用单个线程处理多个连接,通过选择器(Selector)管理多个通道,实现非阻塞的IO操作。
-
缓冲区操作:BIO模型使用字节流或字符流进行数据读写,而NIO模型使用缓冲区(Buffer)进行数据读写,提供了更灵活的数据操作方式。
作用
-
BIO:BIO模型适用于连接数较少、并发要求不高的简单应用场景。它简单易用,可靠性高,适合于对数据完整性和可靠性要求较高的场景。
-
NIO:NIO模型适用于高并发、大规模的应用场景,可以提高系统的并发处理能力和资源利用率。它具有高性能、非阻塞的特性,适合于对并发性能要求较高的场景。