Java NIO(New IO)是 JDK 1.4 引入的一组新的 I/O API,用于支持非阻塞式 I/O 操作。相比传统的 Java
IO API,NIO 提供了更快、更灵活的 I/O 操作方式,可以用于构建高性能网络应用程序。Java NIO 的主要组成部分包括:
Channel:通道是一个在应用程序和文件、网络套接字之间的连接。可以通过通道来进行数据的读取和写入。
Buffer:缓冲区是一个容器,用于存储数据。在 NIO 中,所有的数据读取和写入都是通过缓冲区进行的。
Selector:选择器用于监听多个 NIO 通道的事件,如读写事件。当某个通道发生事件时,选择器会通知该事件并对其进行处理。 相比传统的
Java IO,Java NIO 的优点包括:非阻塞模式:NIO 可以使用非阻塞模式进行网络编程,使程序不必等待网络操作完成才能进行其他操作,提高了程序的响应速度。
多路复用:一个线程可以同时处理多个 NIO 通道,减少了线程的开销和资源占用。 缓冲区操作:NIO
使用缓冲区进行数据读取和写入,可以提高数据访问速度。 下面是 Java NIO 常用类和接口:Channel:提供了各种类型的通道接口,如 FileChannel、DatagramChannel、SocketChannel 和
ServerSocketChannel 等。 Buffer:提供了各种类型的缓冲区实现,如
ByteBuffer、CharBuffer、ShortBuffer 和 DoubleBuffer 等。 Selector:提供了
Selector 接口,用于监听多个通道的事件,可以使用一个线程处理多个通道。 总之,Java NIO
提高了网络编程的效率和性能,使得程序可以处理更多并发请求。但同时需要注意 NIO 的复杂性和学习难度,需要仔细理解其原理和使用规范。
我在早期有讲过Java NIO的基本用法 如果初学者可以 浏览 早期的Java NIO 文章
在Java NIO中,Channel是一个重要的概念,它代表着一个可以进行读写操作的实体,可以与文件、网络套接字等进行交互。
Java NIO提供了多种不同类型的Channel实现类,下面是其中一些常用的实现类:
open(Path path, OpenOption... options)
:静态方法,用于打开一个文件通道。参数path
表示文件路径,options
表示打开文件的选项,例如StandardOpenOption.READ
表示以只读方式打开文件,StandardOpenOption.WRITE
表示以写入方式打开文件等。
read(ByteBuffer dst)
:从通道中读取数据到指定的缓冲区。返回值为读取的字节数,如果返回-1表示已经读取到文件末尾。
write(ByteBuffer src)
:将指定的缓冲区中的数据写入到通道中。返回值为写入的字节数。
position()
和position(long newPosition)
:获取或设置当前通道的位置。position()
方法返回当前位置,position(long newPosition)
方法将位置设置为指定的值。
size()
:获取通道关联文件的大小。
truncate(long size)
:将通道关联文件截断为指定的大小。
force(boolean metaData)
:强制将通道关联文件的内容刷新到磁盘上。
transferTo(long position, long count, WritableByteChannel target)
和transferFrom(ReadableByteChannel src, long position, long count)
:这两个方法用于在通道之间直接传输数据,可以避免数据的中间复制。
lock()
和tryLock()
:用于对通道进行加锁操作,实现文件的独占访问。
close()
:关闭通道。
以上是FileChannel类中的一些常用方法,通过这些方法可以实现对文件的读写操作。需要注意的是,在使用FileChannel进行读写操作时,通常需要结合ByteBuffer类来完成数据的读取和写入。
open()
:静态方法,用于打开一个SocketChannel。
connect(SocketAddress remote)
:连接到指定的远程服务器。参数remote
表示远程服务器的地址。
finishConnect()
:完成连接操作。在调用connect()
方法后,需要调用finishConnect()
方法等待连接完成。
read(ByteBuffer dst)
:从通道中读取数据到指定的缓冲区。返回值为读取的字节数,如果返回-1表示已经读取到流的末尾。
write(ByteBuffer src)
:将指定的缓冲区中的数据写入到通道中。返回值为写入的字节数。
isOpen()
:判断通道是否处于打开状态。
isConnected()
:判断是否已经连接到远程服务器。
socket()
:获取与此通道关联的套接字。
getRemoteAddress()
和getLocalAddress()
:分别获取远程和本地的地址。
setOption(SocketOption
和name, T value) getOption(SocketOption
:用于设置和获取套接字选项。name)
configureBlocking(boolean block)
:设置通道的阻塞模式。如果block
为true,表示通道为阻塞模式,否则为非阻塞模式。
register(Selector sel, int ops)
:将通道注册到指定的选择器中,以便进行选择操作。
close()
:关闭通道。以上是SocketChannel类中的一些常用方法,通过这些方法可以实现网络数据的读取和写入操作。需要注意的是,在使用SocketChannel进行读写操作时,通常需要结合ByteBuffer类来完成数据的读取和写入。
ServerSocketChannel是Java
NIO中用于创建服务器端的通道,它提供了一系列方法来实现服务器的监听和接收客户端连接。以下是ServerSocketChannel类中常用的方法:
open()
:静态方法,用于打开一个ServerSocketChannel。
bind(SocketAddress local)
:绑定服务器的本地地址。参数local
表示本地地址。
accept()
:接受客户端的连接请求,并返回一个SocketChannel用于与客户端进行通信。
configureBlocking(boolean block)
:设置通道的阻塞模式。如果block
为true,表示通道为阻塞模式,否则为非阻塞模式。
isOpen()
:判断通道是否处于打开状态。
socket()
:获取与此通道关联的服务器套接字。
getLocalAddress()
:获取服务器绑定的本地地址。
setOption(SocketOption
和name, T value) getOption(SocketOption
:用于设置和获取套接字选项。name)
register(Selector sel, int ops)
:将通道注册到指定的选择器中,以便进行选择操作。
close()
:关闭通道。以上是ServerSocketChannel类中的一些常用方法,通过这些方法可以实现服务器的监听和接受客户端连接。需要注意的是,在使用ServerSocketChannel时,通常需要结合SocketChannel来与客户端进行通信。
open()
:静态方法,用于打开一个DatagramChannel。
bind(SocketAddress local)
:将通道绑定到指定的本地地址。参数local
表示本地地址。
receive(ByteBuffer dst)
:从通道中接收数据,并将数据存储到指定的缓冲区中。返回值为接收的字节数,如果返回-1表示没有数据可读。
send(ByteBuffer src, SocketAddress target)
:将指定的缓冲区中的数据发送到目标地址。参数src
表示要发送的数据,target
表示目标地址。
configureBlocking(boolean block)
:设置通道的阻塞模式。如果block
为true,表示通道为阻塞模式,否则为非阻塞模式。
isOpen()
:判断通道是否处于打开状态。
socket()
:获取与此通道关联的套接字。
getLocalAddress()
:获取通道绑定的本地地址。
setOption(SocketOption
和name, T value) getOption(SocketOption
:用于设置和获取套接字选项。name)
close()
:关闭通道。以上是DatagramChannel类中的一些常用方法,通过这些方法可以实现UDP数据的发送和接收。需要注意的是,在使用DatagramChannel进行数据发送和接收时,通常需要结合ByteBuffer类来完成数据的读取和写入。
Pipe.SinkChannel常用方法:
write(ByteBuffer src)
:将指定的字节缓冲区中的数据写入到通道中。
close()
:关闭通道。
isOpen()
:判断通道是否处于打开状态。
configureBlocking(boolean block)
:设置通道的阻塞模式。如果block
为true,表示通道为阻塞模式,否则为非阻塞模式。
validOps()
:返回此通道支持的操作集合。Pipe.SourceChannel常用方法:
read(ByteBuffer dst)
:从通道中读取数据,并将数据存储到指定的字节缓冲区中。
close()
:关闭通道。
isOpen()
:判断通道是否处于打开状态。
configureBlocking(boolean block)
:设置通道的阻塞模式。如果block
为true,表示通道为阻塞模式,否则为非阻塞模式。
validOps()
:返回此通道支持的操作集合。需要注意的是,Pipe是一个单向的通道,通过Pipe.SinkChannel向Pipe.SourceChannel传输数据。在使用Pipe进行通信时,一般会创建一个Pipe实例,并通过
sink()
和source()
方法获取对应的SinkChannel和SourceChannel对象,然后在不同的线程中使用这两个通道进行数据的读写操作。
这些Channel实现类都是抽象类,具体的实例化需要通过工厂方法来创建。在使用Channel进行数据读写时,需要结合Buffer类来完成数据的读写操作。
下面是一些Java示例代码,展示了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel、Pipe.SinkChannel和Pipe.SourceChannel的基本用法:
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelExample {
public static void main(String[] args) throws Exception {
RandomAccessFile file = new RandomAccessFile("file.txt", "rw");
FileChannel channel = file.getChannel();
String data = "Hello, World!";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(data.getBytes());
buffer.flip();
channel.write(buffer);
channel.close();
file.close();
}
}
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class SocketChannelExample {
public static void main(String[] args) throws Exception {
SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress("localhost", 8080));
String data = "Hello, Server!";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(data.getBytes());
buffer.flip();
channel.write(buffer);
buffer.clear();
channel.read(buffer);
buffer.flip();
String response = new String(buffer.array(), 0, buffer.limit());
System.out.println("Server response: " + response);
channel.close();
}
}
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class ServerSocketChannelExample {
public static void main(String[] args) throws Exception {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(8080));
SocketChannel channel = serverChannel.accept();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
buffer.flip();
String request = new String(buffer.array(), 0, buffer.limit());
System.out.println("Received request: " + request);
String response = "Hello, Client!";
buffer.clear();
buffer.put(response.getBytes());
buffer.flip();
channel.write(buffer);
channel.close();
serverChannel.close();
}
}
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
public class DatagramChannelExample {
public static void main(String[] args) throws Exception {
DatagramChannel channel = DatagramChannel.open();
channel.bind(new InetSocketAddress(8080));
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.receive(buffer);
buffer.flip();
String request = new String(buffer.array(), 0, buffer.limit());
System.out.println("Received request: " + request);
String response = "Hello, Client!";
buffer.clear();
buffer.put(response.getBytes());
buffer.flip();
channel.send(buffer, new InetSocketAddress("localhost", 8888));
channel.close();
}
}
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
public class PipeChannelExample {
public static void main(String[] args) throws Exception {
Pipe pipe = Pipe.open();
Pipe.SinkChannel sinkChannel = pipe.sink();
Pipe.SourceChannel sourceChannel = pipe.source();
String data = "Hello, Pipe!";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(data.getBytes());
buffer.flip();
sinkChannel.write(buffer);
buffer.clear();
sourceChannel.read(buffer);
buffer.flip();
String response = new String(buffer.array(), 0, buffer.limit());
System.out.println("Received response: " + response);
sinkChannel.close();
sourceChannel.close();
}
}
这些示例代码展示了不同类型的通道的基本用法,可以根据需要进行修改和扩展。请注意,这些示例仅用于演示目的,实际使用时需要进行适当的错误处理和资源释放。
在Java NIO中,有以下几个常用的Buffer实现类:
ByteBuffer:最常用的Buffer实现类,用于读写字节数据。可以通过ByteBuffer.allocate(int capacity)
方法创建一个ByteBuffer实例。
CharBuffer:用于读写字符数据,底层实际上是基于ByteBuffer实现的。可以通过CharBuffer.allocate(int capacity)
方法创建一个CharBuffer实例。
ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer:分别用于读写短整型、整型、长整型、浮点型和双精度浮点型数据。它们都是通过ByteBuffer进行实现的,可以通过相应的allocate
方法创建对应类型的Buffer实例。
这些Buffer实现类都提供了一系列的方法来读写数据、管理指针位置等操作。例如,常用的方法包括:
put()
:向Buffer中写入数据。get()
:从Buffer中读取数据。flip()
:将Buffer从写模式切换到读模式,重置position和limit。clear()
:清空Buffer,重置position、limit和mark。rewind()
:将position重置为0,保持limit不变,可重新读取之前读过的数据。compact()
:将未读完的数据移到Buffer的开头,position设置为已读数据的末尾,limit设置为capacity。这些Buffer实现类提供了灵活的读写操作,可以根据需要选择合适的Buffer类型,并结合通道(Channel)进行数据的读写和传输。
下面是ByteBuffer、CharBuffer和ShortBuffer的示例代码,分别演示了它们的基本用法:
import java.nio.ByteBuffer;
public class ByteBufferExample {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.put((byte) 3);
buffer.flip();
while (buffer.hasRemaining()) {
byte value = buffer.get();
System.out.println(value);
}
}
}
import java.nio.CharBuffer;
public class CharBufferExample {
public static void main(String[] args) {
CharBuffer buffer = CharBuffer.allocate(10);
buffer.put('A');
buffer.put('B');
buffer.put('C');
buffer.flip();
while (buffer.hasRemaining()) {
char value = buffer.get();
System.out.println(value);
}
}
}
import java.nio.ShortBuffer;
public class ShortBufferExample {
public static void main(String[] args) {
ShortBuffer buffer = ShortBuffer.allocate(10);
buffer.put((short) 1);
buffer.put((short) 2);
buffer.put((short) 3);
buffer.flip();
while (buffer.hasRemaining()) {
short value = buffer.get();
System.out.println(value);
}
}
}
这些示例代码演示了ByteBuffer、CharBuffer和ShortBuffer的基本用法。创建Buffer实例后,可以使用put()
方法向缓冲区中写入数据,使用flip()
方法将缓冲区从写模式切换到读模式,然后使用get()
方法读取数据。hasRemaining()
方法用于判断是否还有剩余的数据可读取。
请注意,示例中的容量(capacity)和实际写入的数据量是一致的,这是为了简化代码。在实际应用中,通常会根据需要调整缓冲区的容量,并进行适当的容量检查和数据处理。
在Java NIO中,Selector
是一个多路复用器(Multiplexer)的实现类,用于监控多个通道的状态,以实现单线程处理多个通道的I/O操作。
Selector
类是Java NIO中用于多路复用非阻塞I/O的关键组件。下面是Selector
类常用的方法:
open()
:静态方法,用于创建一个新的Selector
实例。Selector selector = Selector.open();
close()
:关闭选择器。selector.close();
select()
:阻塞等待就绪的通道,返回已就绪通道的数量。int numReadyChannels = selector.select();
select(long timeout)
:阻塞等待就绪的通道,最多等待指定的超时时间(毫秒),返回已就绪通道的数量。int numReadyChannels = selector.select(5000);
selectNow()
:非阻塞地检查是否有就绪的通道,立即返回已就绪通道的数量。int numReadyChannels = selector.selectNow();
wakeup()
:唤醒阻塞在select()
或select(long timeout)
方法上的线程。selector.wakeup();
keys()
:返回当前注册到选择器上的所有选择键(SelectionKey)的集合。Set<SelectionKey> keys = selector.keys();
selectedKeys()
:返回当前就绪的选择键(SelectionKey)的集合。Set<SelectionKey> selectedKeys = selector.selectedKeys();
register(SelectableChannel channel, int interestOps)
:将通道注册到选择器上,并指定感兴趣的事件。channel.register(selector, SelectionKey.OP_READ);
validOps()
:返回选择器支持的操作集合。int validOps = selector.validOps();
cancel()
:取消选择键的注册。selectionKey.cancel();
keys()
:返回当前注册到选择器上的所有选择键(SelectionKey)的集合。Set<SelectionKey> keys = selector.keys();
selectedKeys()
:返回当前就绪的选择键(SelectionKey)的集合。Set<SelectionKey> selectedKeys = selector.selectedKeys();
这些方法提供了对选择器的基本操作,包括创建、关闭、等待就绪通道、注册通道、取消注册等。通过这些方法,可以实现多路复用非阻塞I/O的功能,高效地处理多个通道的I/O事件。在实际应用中,根据具体需求选择合适的方法来管理和操作选择器。
Java NIO中提供了一个名为Selector
的抽象类,它的具体实现类是java.nio.channels.Selector
。通过Selector.open()
方法可以创建一个Selector
实例。
以下是一个简单的示例代码,演示了Selector
的基本用法:
import java.io.IOException;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
public class SelectorExample {
public static void main(String[] args) {
try {
// 创建一个 Selector
Selector selector = Selector.open();
// 创建一个 ServerSocketChannel,并注册到 Selector 上
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 设置为非阻塞模式
serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 注册监听 ACCEPT 事件
// 循环等待事件发生
while (true) {
// 阻塞等待事件发生
int numReadyChannels = selector.select();
// 处理已就绪的事件
if (numReadyChannels > 0) {
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// 处理 ACCEPT 事件
} else if (key.isReadable()) {
// 处理 READ 事件
} else if (key.isWritable()) {
// 处理 WRITE 事件
}
keyIterator.remove(); // 移除已处理的事件
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在示例代码中,首先通过Selector.open()
方法创建一个Selector
实例。然后创建一个ServerSocketChannel
,并将其注册到Selector
上,指定监听OP_ACCEPT
事件。接下来,进入一个无限循环,调用select()
方法阻塞等待事件发生。当有事件就绪时,调用selectedKeys()
方法获取已就绪的事件集合,然后遍历处理每个事件。
在实际应用中,可以根据需要注册不同类型的事件,如OP_READ
、OP_WRITE
等,并在处理事件时执行相应的操作。同时,还可以使用Selector
的其他方法,如wakeup()
、selectNow()
等,来控制选择器的行为。
需要注意的是,Selector
是线程安全的,可以在多个线程中共享一个选择器。但是,对于同一个选择器,不应该在多个线程中同时调用select()
方法或修改注册的通道和事件。通常建议将选择器的操作放在单独的线程中执行,以避免竞争条件和线程安全问题。
以下是Java NIO中常用的Channel、Buffer和Selector的使用代码模板示例代码:
Channel示例代码:
// 创建一个SocketChannel并连接到指定地址
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
// 判断是否连接成功
while (!socketChannel.finishConnect()) {
// 连接未完成,继续等待
}
// 发送数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello, Server!".getBytes());
buffer.flip();
socketChannel.write(buffer);
// 接收数据
buffer.clear();
socketChannel.read(buffer);
buffer.flip();
System.out.println(new String(buffer.array()));
// 关闭通道
socketChannel.close();
Buffer示例代码:
// 创建一个ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 写入数据
buffer.put("Hello, World!".getBytes());
// 读取数据
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
System.out.println(new String(bytes));
// 清空缓冲区
buffer.clear();
Selector示例代码:
// 创建一个Selector
Selector selector = Selector.open();
// 创建一个ServerSocketChannel并绑定到指定端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
// 将ServerSocketChannel注册到Selector上,并监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 等待事件发生
while (selector.select() > 0) {
// 获取所有已经就绪的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 处理所有已经就绪的事件
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
// 处理连接事件
ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 处理读取事件
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = socketChannel.read(buffer)) > 0) {
buffer.flip();
System.out.println(new String(buffer.array(), 0, len));
buffer.clear();
}
if (len == -1) {
// 客户端关闭连接
socketChannel.close();
}
}
// 移除已经处理过的事件
iterator.remove();
}
}
// 关闭Selector和ServerSocketChannel
selector.close();
serverSocketChannel.close();
以上是Java NIO中常用的Channel、Buffer和Selector的使用代码模板示例代码。需要注意的是,这些代码仅供参考,实际使用时需要根据具体情况进行调整。