Java NIO(New I/O)是 Java 编程语言中用于非阻塞 IO 操作的一组 API。它提供了更为灵活和高效的 IO 操作方式,相比传统的 Java IO,Java NIO 提供了以下优点:
-
通道与缓冲区:Java NIO 中引入了通道(Channel)和缓冲区(Buffer)的概念,可以实现直接的数据传输,避免了传统 IO 中频繁的数据拷贝操作。
-
非阻塞 IO:Java NIO 支持非阻塞 IO 操作,通过选择器(Selector)可以实现同时管理多个通道的IO操作。
-
内存映射文件:Java NIO 提供了对文件的内存映射,可以将文件直接映射到内存中,从而实现快速的文件读写操作。
-
多路复用:Java NIO 中的选择器(Selector)可以同时监控多个通道的IO事件,降低系统开销并提高效率。
总的来说,Java NIO 提供了更加灵活和高效的IO操作方式,适用于需要处理大量连接或需要高性能IO操作的场景。
Java NIO(New I/O)包提供了用于非阻塞 I/O 操作的类和方法,主要涉及以下几个重要类:
1. Buffer 类族:
- ByteBuffer:用于处理字节数据的缓冲区。
- CharBuffer:用于处理字符数据的缓冲区。
- ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer:分别用于处理基本数据类型的缓冲区。
2. 通道(Channel):
- Channel:表示连接到实体(如文件、套接字、选择键等)的开放连接,可进行读取、写入、映射和锁定操作。
- FileChannel:用于文件 I/O 操作的通道。
- SocketChannel:用于套接字连接的通道。
- ServerSocketChannel:用于服务器套接字的通道。
- DatagramChannel:用于 UDP 数据报传输的通道。
3. Selector 和 SelectionKey:
- Selector:允许单线程处理多个 Channel 的机制,可以通过它实现非阻塞 I/O。
- SelectionKey:代表了一个特定的通道与选择器的注册关系,以及通道所关心的 I/O 事件。
4. Path、Files 和 FileSystems:
- Path:表示文件系统中的路径,提供了大量的方法来操作文件路径。
- Files:提供了文件 I/O 相关的静态方法,用于文件的创建、复制、移动、删除等操作。
- FileSystems:表示文件系统,提供了获取文件系统的方法。
一些重要的方法包括:
- 缓冲区的 put()、get()、flip()、clear()、rewind() 等方法,用于写入、读取、切换模式和重置位置等操作。
- 通道的 read()、write()、close() 方法用于读写数据和关闭通道。
- 选择器的 select()、wakeup() 方法和选择键的 interestOps()、readyOps() 方法,用于实现非阻塞 I/O 多路复用。
这些类和方法为 Java 提供了更灵活和高效的 I/O 操作方式,特别适用于网络编程和需要高性能的 I/O 场景。
---------------
SelectionKey 是 Java NIO 中的一个关键概念,它代表了一个通道在选择器(Selector)中的注册。当你向选择器注册一个通道时,会创建一个对应的 SelectionKey 对象,用于表示这个注册关系,并可以用于识别通道在选择器上的状态和事件。
在 NIO 中,SelectionKey 包含了以下信息:
1. 通道(Channel):与这个 SelectionKey 相关联的通道。
2. 注册的选择器(Selector):这个 SelectionKey 所在的选择器。
3. 可选择的操作集(Interested Operation Set):表示这个通道所关心的 I/O 事件类型,比如读、写、连接、接受等。
4. 就绪的操作集(Ready Operation Set):表示这个通道当前已经就绪的 I/O 事件类型。
通过 SelectionKey,你可以将一个通道注册到选择器上,并且根据不同的事件类型进行相应的处理。在你的代码示例中,使用 SelectionKey 来表示已经就绪的通道以及相应的处理操作,如接受连接、读取数据等。
----------
在 Java NIO 中,`Selector` 和 `SelectionKey` 是多路复用的关键组件,用于实现非阻塞 I/O 操作。让我来简单解释它们之间的关系:
1. `Selector`(选择器):`Selector` 是一个可以检测多个通道上事件的对象,它可以检测这些通道是否已经准备好进行 I/O 操作。通过 `Selector`,你可以同时管理多个通道,而不需要为每个通道创建一个线程,从而提高系统的性能和效率。
2. `SelectionKey`(选择键):`SelectionKey` 是 `Selector` 和通道之间的桥梁。当你将一个通道注册到一个 `Selector` 上时,会创建一个对应的 `SelectionKey` 对象,用于表示通道在 `Selector` 上的注册状态和感兴趣的 I/O 事件。每个 `SelectionKey` 包含了与之关联的通道、其注册的 `Selector`、感兴趣的操作集合以及就绪的操作集合。
基本流程是这样的:你首先创建一个 `Selector` 对象,然后将一个或多个通道注册到这个 `Selector` 上,并指定感兴趣的 I/O 事件。`Selector` 会根据这些感兴趣的事件监听通道的状态,并返回就绪的 `SelectionKey` 集合。接着你可以通过遍历这些就绪的 `SelectionKey` 来处理相应的 I/O 操作。
因此,`Selector` 和 `SelectionKey` 一起构成了 Java NIO 中实现非阻塞 I/O 的核心机制。
------------
java.nio.ByteBuffer 类提供了一系列方法来操作字节数据缓冲区,下面是一些常用的方法及其作用:
1. allocate(int capacity): 静态方法,用于分配一个新的 ByteBuffer 实例,并指定容量为 capacity。返回一个新的 ByteBuffer 对象。
2. put(byte b): 将一个字节写入缓冲区的当前位置,并将位置自动递增。
3. put(byte[] src, int offset, int length): 将字节数组 src 中从偏移量 offset 处开始的 length 个字节写入到缓冲区中。
4. get(): 读取缓冲区当前位置的字节,并将位置自动递增。
5. get(byte[] dst, int offset, int length): 将缓冲区中的数据从当前位置开始读取到字节数组 dst 中,从偏移量 offset 处开始写入 length 个字节。
6. flip(): 切换缓冲区为读模式,将限制设置为当前位置,然后将位置设为 0,准备开始读取数据。
7. clear(): 清空缓冲区,将位置设置为 0,限制设置为容量,丢弃标记。
8. rewind(): 将位置设为 0,不改变限制,可以重新读取已经读过的数据。
9. compact(): 将未读完的数据复制到缓冲区的起始位置,然后将位置设置为复制的数据末尾,限制设置为容量。
10. mark(): 在当前位置设置一个标记。
11. reset(): 将位置恢复到之前设置的标记位置。
12. remaining(): 返回当前位置和限制之间的元素个数。
13. hasRemaining(): 判断是否还有剩余的元素可读或者写。
14. slice(): 创建一个新的 ByteBuffer,共享原始缓冲区的内容。
15. asReadOnlyBuffer(): 返回一个只读的 ByteBuffer 视图。
这些方法可以帮助您有效地处理字节数据,进行读写操作并管理缓冲区的状态。根据具体的需求和场景,选择合适的方法来操作 ByteBuffer,以实现高效的字节数据处理。
------
服务器代码。服务器程序主要做了以下几件事情:
- 创建一个 Selector 对象,用于多路复用非阻塞 I/O 事件。
- 创建一个 ServerSocketChannel 对象,并绑定到指定端口,然后将其注册到 Selector 上,并监听连接事件(SelectionKey.OP_ACCEPT)。
- 进入循环,在循环中调用 Selector 的 select() 方法阻塞等待就绪事件,一旦有就绪事件发生,就会返回就绪通道的数量。
- 如果有就绪的通道,就通过 selectedKeys() 方法获得 SelectionKey 集合,遍历处理每一个就绪的事件。
- 如果是接受连接事件(isAcceptable()),则通过 ServerSocketChannel.accept() 接受客户端连接,并将客户端的 SocketChannel 注册到 Selector 上,监听读事件(SelectionKey.OP_READ)。
- 如果是可读事件(isReadable()),则从 SocketChannel 中读取数据并进行处理。
总的来说,服务器程序使用 Selector 实现了单线程处理多个 Channel 的非阻塞 I/O,能够更高效地处理大量并发连接。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class serverSocket_NIOSelectorExample {
public static void main(String[] args) throws IOException {
// 创建一个 Selector,用于多路复用 I/O 事件
Selector selector = Selector.open();
// 创建一个 ServerSocketChannel,并绑定到指定端口,然后注册到 Selector 上
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(8888)); // 将 ServerSocketChannel 绑定到指定端口
serverSocket.configureBlocking(false); // 将 ServerSocketChannel 设置为非阻塞模式
serverSocket.register(selector, SelectionKey.OP_ACCEPT); // 注册 ServerSocketChannel 到 Selector,监听连接事件
while (true) { // 进入事件处理循环
// 阻塞直到有就绪事件发生
int readyChannels = selector.select();
if (readyChannels == 0) {
continue; // 如果没有就绪的事件,继续等待
}
Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 获取所有就绪的 SelectionKey 集合
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
// 处理就绪事件
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// 有连接建立事件
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept(); // 接受客户端连接
client.configureBlocking(false); // 将客户端连接设置为非阻塞模式
client.register(selector, SelectionKey.OP_READ); // 将客户端 SocketChannel 注册到 Selector,监听读事件
System.out.println("Accepted connection from " + client.getRemoteAddress());
} else if (key.isReadable()) {
// 有可读事件
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer); // 从客户端读取数据到缓冲区
if (bytesRead != -1) {
buffer.flip(); // 切换缓冲区为读模式
byte[] data = new byte[buffer.limit()];
buffer.get(data); // 从缓冲区中获取数据
System.out.println("Received: " + new String(data));
} else {
client.close(); // 客户端连接关闭
}
}
keyIterator.remove(); // 处理完一个事件后需要手动移除,防止重复处理
}
}
}
}
=--------
创建了一个简单的客户端程序,通过 SocketChannel 连接到指定的服务器地址和端口(这里是 localhost:8888),然后发送一些字符串消息给服务器。每条消息发送之后,程序会休眠3秒钟。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClientExample {
public static void main(String[] args) throws IOException, InterruptedException {
InetSocketAddress hostAddress = new InetSocketAddress("localhost", 8888);
SocketChannel client = SocketChannel.open(hostAddress);
System.out.println("Client... started");
String[] messages = new String[]{"Time goes fast.", "What's up?", "Bye!"};
for (String message : messages) {
byte[] messageBytes = message.getBytes();
ByteBuffer buffer = ByteBuffer.wrap(messageBytes);
client.write(buffer);
System.out.println(message);
buffer.clear();
Thread.sleep(3000);
}
client.close();
}
}