在网络编程的世界里,I/O操作是至关重要的组成部分,但传统的阻塞式I/O模型却常常成为性能瓶颈。想象一下,当你在一台服务器上处理数千个并发连接时,每个连接都需要一个独立的线程来处理读写操作。这不仅消耗了大量的系统资源,还导致了严重的上下文切换开销。于是,Java NIO(Non-blocking I/O)应运而生,为我们带来了非阻塞I/O的解决方案,开启了高并发处理的新纪元。

第二部分:非阻塞I/O的实践案例

实例1:基于NIO的简单聊天服务器

我们将构建一个简单的聊天服务器,使用NIO的非阻塞模式接收来自多个客户端的消息并广播给所有在线用户。

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.HashSet;
import java.util.Set;
import java.util.Iterator;

public class ChatServer {
    private static final int PORT = 8080;
    private static final ByteBuffer BUFFER_SIZE = ByteBuffer.allocate(1024);
    private static final Set<SocketChannel> clients = new HashSet<>();

    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(PORT));
        serverChannel.configureBlocking(false);
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                if (key.isAcceptable()) {
                    acceptNewClient(serverChannel, selector);
                }
                if (key.isReadable()) {
                    readFromClient((SocketChannel) key.channel());
                }
                it.remove();
            }
        }
    }

    private static void acceptNewClient(ServerSocketChannel serverChannel, Selector selector) throws IOException {
        SocketChannel client = serverChannel.accept();
        client.configureBlocking(false);
        client.register(selector, SelectionKey.OP_READ);
        clients.add(client);
    }

    private static void readFromClient(SocketChannel client) throws IOException {
        BUFFER_SIZE.clear();
        int bytesRead = client.read(BUFFER_SIZE);
        if (bytesRead == -1) {
            clients.remove(client);
            client.close();
        } else {
            BUFFER_SIZE.flip();
            byte[] data = new byte[BUFFER_SIZE.limit()];
            BUFFER_SIZE.get(data);
            String message = new String(data).trim();
            System.out.println("Received: " + message);
            broadcastToAll(message, client);
            BUFFER_SIZE.clear();
        }
    }

    private static void broadcastToAll(String message, SocketChannel sender) throws IOException {
        for (SocketChannel client : clients) {
            if (client != sender) {
                ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
                client.write(buffer);
            }
        }
    }
}

在这个例子中,我们首先创建了一个Selector并打开了一个ServerSocketChannel,将其绑定到一个端口上。然后,我们在一个无限循环中使用Selector来监听新连接和已连接客户端的可读事件。当有新的客户端连接时,我们接受这个连接并将SocketChannel注册到Selector上,设置其为非阻塞模式。当SocketChannel上有可读事件时,我们读取数据并广播给所有其他在线的客户端。

实例2:文件复制

下面是一个使用NIO进行文件复制的例子,展示如何使用FileChannelByteBuffer进行高效的数据传输。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.channels.FileChannel;

public class FileCopier {
    public static void main(String[] args) {
        String sourcePath = "source.txt";
        String destPath = "destination.txt";

        try (FileChannel inChannel = FileChannel.open(Paths.get(sourcePath), StandardOpenOption.READ);
             FileChannel outChannel = FileChannel.open(Paths.get(destPath), StandardOpenOption.WRITE,
                                                      StandardOpenOption.CREATE_NEW,
                                                      StandardOpenOption.TRUNCATE_EXISTING)) {

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while (inChannel.read(buffer) > 0) {
                buffer.flip();
                outChannel.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们首先打开源文件和目标文件的FileChannel,然后使用一个ByteBuffer作为中介来读取和写入数据。FileChannelread()write()方法分别用于填充ByteBuffer和清空它,从而实现了数据的高效传输。