Java的旧 IO(java.io 包)和新 IO(java.nio 包)都是用于文件和网络 I/O 操作的 Java 标准库。它们各自都有优缺点,所以Java开发人员可以根据自己的需求选择使用哪种 IO 库。
旧 IO 是较早的 Java I/O库,它使用流(Stream)的概念来处理数据。旧 IO 库的 API 简单易懂,但在处理大量数据时性能较差。因为它在读取或写入数据时,必须等待整个流操作完成后才能进行下一步操作。这种同步阻塞的方式,导致了较低的性能。
新IO(NIO)是在 Java 1.4 中引入的,相对于旧 IO 而言,它更加灵活、高效。NIO 提供了一种新的 I/O 操作方式,它基于通道(Channel)和缓冲区(Buffer)的概念,使用非阻塞的方式处理 I/O 操作,能够更好地适应现代的高并发环境。它的 API 较为复杂,但能够提供更高效的 I/O 操作。
因此,新 IO 主要用于处理需要高效处理 I/O 的场景,例如:大数据量的文件操作、网络编程等。旧 IO 主要用于简单的 I/O 操作,例如:读写少量数据、文件系统操作等。
Java NIO 的优点
相比于传统的阻塞 IO 操作,Java NIO 具有以下几个优点:
高效的 IO 操作
Java NIO 可以使用选择器(Selector)来监视多个通道(Channel),在通道准备就绪时自动通知应用程序,从而避免了阻塞等待 IO 操作完成的情况。这样可以使得 IO 操作更加高效,特别是在同时处理多个连接或数据流时。
支持多种 IO 操作类型
Java NIO 支持多种 IO 操作类型,如文件 IO、网络 IO、管道 IO 等。这些操作类型都可以使用相同的 API 进行操作,使得编程更加简单和一致。
更好的内存管理
Java NIO 使用缓冲区(Buffer)来缓存读取或写入的数据,缓冲区支持批量读取或写入,这样可以避免频繁的内存分配和释放操作,提高了内存的利用率和程序的性能。
可靠的错误处理
Java NIO API 提供了可靠的错误处理机制,可以更好地处理 IO 操作中出现的异常和错误情况。主要有以下几个方面的原因:
总的来说,Java NIO API 使得开发人员可以更有效地处理非阻塞 IO 操作,提高了程序的性能和可靠性,特别是在同时处理多个连接或数据流时。如果你需要处理高并发、高吞吐量的网络应用程序或文件操作程序,那么 Java NIO 就是一个非常好的选择。
Java NIO API 的基本组成部分
以下是 Java NIO API 的基本组成部分:
缓冲区(Buffer)
缓冲区(Buffer)是 Java NIO API 中的一个重要概念,它是用于在通道(Channel)和 IO 操作之间传递数据的临时存储区域。缓冲区可以存储不同类型的数据,如字节(Byte)、字符(Char)、整数(Int)等。
下面是一个简单的缓冲区示例代码,用于存储和读取字节数据:
package cn.leetcode.niodemo;
import java.nio.ByteBuffer;
public class BufferExample {
public static void main(String[] args) {
// 创建一个缓冲区,大小为 10 字节
ByteBuffer buffer = ByteBuffer.allocate(10);
// 写入数据到缓冲区
buffer.put((byte) 'H');
buffer.put((byte) 'e');
buffer.put((byte) 'l');
buffer.put((byte) 'l');
buffer.put((byte) 'o');
// 将缓冲区从写模式切换到读模式
buffer.flip();
// 读取数据
while (buffer.hasRemaining()) {
byte b = buffer.get();
System.out.print((char) b);
}
}
}
输出:
Hello
上面的代码创建了一个大小为 10 字节的缓冲区,并使用 put() 方法将字节数据写入缓冲区中。然后,使用 flip() 方法将缓冲区从写模式切换到读模式,这样就可以从缓冲区中读取数据了。最后,使用 hasRemaining() 方法检查缓冲区中是否还有剩余的数据,如果有,则使用 get() 方法读取数据。
缓冲区还有很多其他的方法,如 clear() 方法清空缓冲区、compact() 方法将缓冲区中的数据移到缓冲区的起始位置、mark() 和 reset() 方法标记和重置缓冲区等。了解这些方法对于正确使用缓冲区非常重要。
通道(Channel)
通道(Channel)是 Java NIO API 中的一个重要概念,它是一种与底层 IO 设备交互的对象。通道可以使用不同的通道类型,如文件(FileChannel)、网络(SocketChannel、ServerSocketChannel、DatagramChannel)等。
下面是一个简单的通道示例代码,用于读取文件数据:
package cn.leetcode.niodemo;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ChannelExample {
public static void main(String[] args) throws Exception {
try (FileInputStream fileInputStream = new FileInputStream("input.txt");
// 打开文件通道
FileChannel channel = fileInputStream.getChannel()) {
// 创建一个缓冲区,大小为 10 字节
ByteBuffer buffer = ByteBuffer.allocate(10);
// 从通道中读取数据到缓冲区
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead + " bytes");
// 将缓冲区从写模式切换到读模式
buffer.flip();
// 读取缓冲区中的数据
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
// 清空缓冲区
buffer.clear();
bytesRead = channel.read(buffer);
}
}
}
}
上面的代码打开了一个文件通道,使用 read() 方法从通道中读取数据到缓冲区中,然后使用 flip() 方法将缓冲区从写模式切换到读模式,最后使用 get() 方法读取缓冲区中的数据。如果缓冲区中还有剩余的数据,则需要再次读取,直到读取完成。最后,使用 close() 方法关闭通道和文件输入流。
通道还有很多其他的方法,如 write() 方法将数据写入通道、transferFrom() 和 transferTo() 方法在通道之间传输数据、position() 和 position(long newPosition) 方法设置和获取通道的位置等。了解这些方法对于正确使用通道非常重要。
选择器(Selector)
选择器(Selector)是 Java NIO API 中的一个重要概念,它可以用于监视多个通道(Channel),在通道准备就绪时自动通知应用程序。选择器使得应用程序可以在一个线程中处理多个非阻塞 IO 操作,从而提高了程序的性能和可扩展性。
可以通过以下的通俗易懂的例子来理解 Java NIO 中的 Selector :假设有一家快递公司需要同时处理多个快递订单。每个订单都有不同的状态,例如待发货、已发货、已签收等。为了能够高效地处理这些订单,快递公司雇佣了多名快递员,每名快递员可以处理多个订单。
在这个例子中,快递订单可以看做是 Java NIO 中的通道(Channel),快递员可以看做是 Java NIO 中的选择器(Selector),不同的订单状态可以看做是 Java NIO 中的事件(Event)。选择器(快递员)可以同时监控多个通道(订单),当有事件(订单状态发生变化)时,选择器(快递员)会立即响应并进行相应的处理(例如将待发货的订单状态改为已发货)。
下面是一个简单的选择器示例代码,用于监听多个 Socket 连接:
package cn.leetcode.niodemo;
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 Server {
private static final int PORT = 8080;
private static final int BUFFER_SIZE = 1024;
private static final String CHARSET = "UTF-8";
public static void main(String[] args) throws IOException {
// 创建 Selector
Selector selector = Selector.open();
// 创建 ServerSocketChannel,并绑定端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(PORT));
serverSocketChannel.configureBlocking(false);
// 将 ServerSocketChannel 注册到 Selector,并监听 ACCEPT 事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞等待事件发生
selector.select();
// 获取发生事件的所有通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 处理 ACCEPT 事件
if (selectionKey.isAcceptable()) {
ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
// 处理 READ 事件
if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
StringBuilder content = new StringBuilder();
while (socketChannel.read(buffer) > 0) {
buffer.flip();
content.append(new String(buffer.array(), 0, buffer.remaining(), CHARSET));
}
System.out.println("Received data from client: " + content.toString());
// 将数据回显给客户端
buffer.clear();
buffer.put(("Echo message: " + content.toString()).getBytes());
buffer.flip();
socketChannel.write(buffer);
}
// 处理完事件后,需要将选择键清空
iterator.remove();
}
}
}
}
在上述代码中,首先创建 Selector 对象,并将 ServerSocketChannel 注册到 Selector 中,监听 ACCEPT 事件。当有客户端连接时,会触发 ACCEPT 事件,然后将 SocketChannel 注册到 Selector 中,监听 READ 事件。当客户端发送数据时,会触发 READ 事件,从而实现对客户端请求的处理和回显。
值得注意的是,这里的 socketChannel.configureBlocking(false) 方法将 SocketChannel 设置为非阻塞模式,从而可以实现非阻塞 IO 操作。同时,在处理完事件后,需要将选择键清空,否则会导致重复处理相同的事件。
可以使用 telnet 工具来测试该代码的效果。
以下是一个示例会话:
telnet localhost 8080
Trying ::1...
Connected to localhost.
Escape character is '^]'.
Hello LeetCoder!
Echo message: Hello LeetCoder!
在该示例会话中,我们首先连接到本地的8080端口,然后向服务端发送了字符串 Hello LeetCoder!。服务端接收到该字符串,并回显给客户端 Echo message: Hello LeetCoder!。
使用 Java NIO API 进行非阻塞 IO 操作的基本步骤
阅读量:2022
点赞量:0
收藏量:0