Java NIO案例

wen java案例 3

本文目录导读:

Java NIO案例

  1. Java NIO核心组件
  2. 基础案例:文件复制
  3. Socket通信案例
  4. 文件映射和内存映射
  5. 完整示例:NIO文件上传下载系统
  6. 最佳实践建议
  7. 性能对比

下面我为你详细讲解Java NIO(New I/O)的完整案例,包含核心概念和实际代码示例。

Java NIO核心组件

Java NIO主要包含三大核心组件:

  • Channel(通道):数据传输的载体
  • Buffer(缓冲区):数据存储的容器
  • Selector(选择器):多路复用器,监控多个通道

基础案例:文件复制

1 使用FileChannel复制文件

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileChannelDemo {
    public static void main(String[] args) {
        String sourceFile = "source.txt";
        String targetFile = "target.txt";
        try {
            copyFile(sourceFile, targetFile);
            System.out.println("文件复制成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 使用FileChannel复制文件
     */
    public static void copyFile(String source, String target) throws IOException {
        // 打开文件通道
        Path sourcePath = Paths.get(source);
        Path targetPath = Paths.get(target);
        try (FileChannel sourceChannel = FileChannel.open(sourcePath, StandardOpenOption.READ);
             FileChannel targetChannel = FileChannel.open(targetPath, StandardOpenOption.WRITE, 
                                                          StandardOpenOption.CREATE)) {
            // 创建缓冲区(大小为8KB)
            ByteBuffer buffer = ByteBuffer.allocate(8192);
            // 从源通道读取数据到缓冲区
            while (sourceChannel.read(buffer) != -1) {
                // 切换为读模式
                buffer.flip();
                // 将缓冲区数据写入目标通道
                while (buffer.hasRemaining()) {
                    targetChannel.write(buffer);
                }
                // 清空缓冲区,准备下一次读取
                buffer.clear();
            }
        }
    }
}

2 使用transferTo实现零拷贝

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class ZeroCopyDemo {
    public static void main(String[] args) throws IOException {
        String sourceFile = "large_file.zip";
        String targetFile = "large_file_copy.zip";
        // 零拷贝方式复制文件
        try (FileChannel sourceChannel = FileChannel.open(Paths.get(sourceFile), StandardOpenOption.READ);
             FileChannel targetChannel = FileChannel.open(Paths.get(targetFile), StandardOpenOption.WRITE, 
                                                          StandardOpenOption.CREATE)) {
            long position = 0;
            long size = sourceChannel.size();
            // 使用transferTo实现零拷贝
            while (position < size) {
                position += sourceChannel.transferTo(position, size - position, targetChannel);
            }
            System.out.println("零拷贝复制完成!");
        }
    }
}

Socket通信案例

1 阻塞式Socket通信

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BlockingSocketDemo {
    // 服务器端
    public static class Server {
        public static void main(String[] args) throws IOException {
            int port = 8080;
            // 创建ServerSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(port));
            System.out.println("服务器启动,监听端口:" + port);
            // 创建线程池处理客户端请求
            ExecutorService executor = Executors.newFixedThreadPool(10);
            while (true) {
                // 接受客户端连接(阻塞)
                SocketChannel socketChannel = serverSocketChannel.accept();
                System.out.println("客户端连接:" + socketChannel.getRemoteAddress());
                // 为每个客户端创建独立线程
                executor.submit(() -> handleClient(socketChannel));
            }
        }
        private static void handleClient(SocketChannel socketChannel) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            try {
                // 读取客户端发送的数据
                while (socketChannel.read(buffer) != -1) {
                    buffer.flip();
                    // 将数据转换字符串并打印
                    String message = new String(buffer.array(), 0, buffer.limit());
                    System.out.println("收到客户端消息:" + message);
                    // 回显消息给客户端
                    String response = "服务器已收到:" + message;
                    buffer.clear();
                    buffer.put(response.getBytes());
                    buffer.flip();
                    socketChannel.write(buffer);
                    buffer.clear();
                }
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    // 客户端
    public static class Client {
        public static void main(String[] args) throws IOException {
            // 连接服务器
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("localhost", 8080));
            // 发送多条消息
            String[] messages = {"Hello", "World", "Java NIO"};
            for (String message : messages) {
                // 发送消息
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                buffer.put(message.getBytes());
                buffer.flip();
                socketChannel.write(buffer);
                // 读取响应
                buffer.clear();
                socketChannel.read(buffer);
                buffer.flip();
                String response = new String(buffer.array(), 0, buffer.limit());
                System.out.println("服务器响应:" + response);
                Thread.sleep(1000);
            }
            socketChannel.close();
        }
    }
}

2 非阻塞式Socket通信(Selector多路复用)

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class NonBlockingSocketDemo {
    public static void main(String[] args) throws IOException {
        int port = 8090;
        Selector selector = Selector.open();
        // 创建ServerSocketChannel并设置为非阻塞
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.bind(new InetSocketAddress(port));
        // 注册接受连接事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("非阻塞式服务器启动,端口:" + port);
        // 存储客户端数据处理任务
        ConcurrentHashMap<SocketChannel, StringBuilder> clientData = new ConcurrentHashMap<>();
        while (true) {
            // 阻塞直到有事件发生
            selector.select();
            // 获取所有事件
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                // 处理不同的事件
                if (key.isAcceptable()) {
                    // 接受连接
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    // 注册读事件
                    client.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端连接:" + client.getRemoteAddress());
                    clientData.put(client, new StringBuilder());
                } else if (key.isReadable()) {
                    // 读取数据
                    handleRead(key, clientData);
                }
            }
        }
    }
    private static void handleRead(SelectionKey key, 
                                  ConcurrentHashMap<SocketChannel, StringBuilder> clientData) {
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try {
            int bytesRead = client.read(buffer);
            if (bytesRead == -1) {
                // 连接关闭
                clientData.remove(client);
                client.close();
                key.cancel();
                return;
            }
            buffer.flip();
            String data = new String(buffer.array(), 0, buffer.limit());
            System.out.println("收到数据:" + data);
            // 处理数据
            if (data.startsWith("START:")) {
                String fileName = data.substring(6);
                clientData.get(client).append("文件开始接收:" + fileName + "\n");
                // 发送确认
                sendResponse(client, "文件传输开始");
            } else if (data.equals("END")) {
                clientData.get(client).append("文件传输结束");
                String fullData = clientData.get(client).toString();
                System.out.println("完整文件数据:\n" + fullData);
                clientData.put(client, new StringBuilder());
            } else {
                clientData.get(client).append(data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static void sendResponse(SocketChannel client, String message) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put(message.getBytes());
        buffer.flip();
        client.write(buffer);
    }
}

文件映射和内存映射

import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class MappedFileDemo {
    public static void main(String[] args) throws IOException {
        // 创建文件
        Path filePath = Paths.get("mapped_file.txt");
        Files.deleteIfExists(filePath);
        Files.createFile(filePath);
        // 内存映射写入
        writeWithMapping(filePath);
        // 内存映射读取
        readWithMapping(filePath);
    }
    /**
     * 使用内存映射写入文件
     */
    public static void writeWithMapping(Path filePath) throws IOException {
        try (FileChannel channel = FileChannel.open(filePath, 
                                                   StandardOpenOption.READ, 
                                                   StandardOpenOption.WRITE)) {
            // 映射到内存(大小为文件大小或更大)
            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
            // 写入数据
            String content = "这是一个内存映射文件示例\n";
            buffer.put(content.getBytes());
            buffer.put("Java NIO内存映射".getBytes());
            System.out.println("内存映射写入完成");
        }
    }
    /**
     * 使用内存映射读取文件
     */
    public static void readWithMapping(Path filePath) throws IOException {
        try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
            // 映射到只读模式
            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
            // 读取数据
            byte[] data = new byte[buffer.limit()];
            buffer.get(data);
            String content = new String(data);
            System.out.println("读取内容:\n" + content);
        }
    }
}

完整示例:NIO文件上传下载系统

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class FileTransferSystem {
    // 服务器端
    public static class FileServer {
        private static Map<SocketChannel, FileTransferState> transfers = new HashMap<>();
        public static void main(String[] args) throws IOException {
            Selector selector = Selector.open();
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);
            serverChannel.bind(new InetSocketAddress(8081));
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("文件服务器启动...");
            while (true) {
                selector.select();
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    // 处理连接请求
                    if (key.isAcceptable()) {
                        handleAccept(key);
                    }
                    // 处理读取事件
                    if (key.isReadable()) {
                        handleRead(key);
                    }
                }
            }
        }
        private static void handleAccept(SelectionKey key) throws IOException {
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
            // 初始化传输状态
            FileTransferState state = new FileTransferState();
            transfers.put(client, state);
            System.out.println("客户端连接:" + client.getRemoteAddress());
        }
        private static void handleRead(SelectionKey key) {
            SocketChannel client = (SocketChannel) key.channel();
            FileTransferState state = transfers.get(client);
            ByteBuffer buffer = ByteBuffer.allocate(8192);
            try {
                int bytesRead = client.read(buffer);
                if (bytesRead == -1) {
                    // 连接关闭
                    transfers.remove(client);
                    client.close();
                    key.cancel();
                    return;
                }
                buffer.flip();
                // 处理收到的数据
                while (buffer.hasRemaining()) {
                    state.processData(buffer);
                }
            } catch (IOException e) {
                try {
                    client.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
        // 文件传输状态管理器
        static class FileTransferState {
            private String fileName;
            private long fileSize;
            private long receivedSize;
            private Path outputPath;
            private FileChannel outputChannel;
            private boolean isReceiving;
            public void processData(ByteBuffer buffer) throws IOException {
                // 如果还没有收到文件名,首先解析文件名
                if (!isReceiving) {
                    byte[] header = new byte[Math.min(buffer.limit(), 256)];
                    buffer.get(header, 0, header.length);
                    String headerStr = new String(header).trim();
                    if (headerStr.contains(":")) {
                        fileName = headerStr.split(":")[0];
                        fileSize = Long.parseLong(headerStr.split(":")[1]);
                        receivedSize = 0;
                        // 创建输出文件
                        outputPath = Paths.get("uploads", fileName);
                        Files.createDirectories(outputPath.getParent());
                        outputChannel = FileChannel.open(outputPath, 
                                                        StandardOpenOption.CREATE, 
                                                        StandardOpenOption.WRITE);
                        isReceiving = true;
                        System.out.println("开始接收文件:" + fileName + ",大小:" + fileSize);
                    }
                }
                // 接收文件数据
                if (isReceiving && buffer.hasRemaining()) {
                    outputChannel.write(buffer);
                    receivedSize += buffer.limit();
                    // 检查是否接收完成
                    if (receivedSize >= fileSize) {
                        outputChannel.close();
                        System.out.println("文件接收完成:" + fileName);
                        isReceiving = false;
                    }
                }
            }
        }
    }
    // 客户端
    public static class FileClient {
        public static void main(String[] args) {
            String serverIp = "localhost";
            int serverPort = 8081;
            String filePath = "test.txt";
            try {
                sendFile(serverIp, serverPort, filePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public static void sendFile(String serverIp, int serverPort, String filePath) throws IOException {
            // 连接服务器
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress(serverIp, serverPort));
            // 打开文件
            Path file = Paths.get(filePath);
            long fileSize = Files.size(file);
            FileChannel fileChannel = FileChannel.open(file, StandardOpenOption.READ);
            // 发送文件头信息
            String header = file.getFileName() + ":" + fileSize;
            ByteBuffer headerBuffer = ByteBuffer.wrap(header.getBytes());
            socketChannel.write(headerBuffer);
            // 发送文件数据
            ByteBuffer buffer = ByteBuffer.allocate(8192);
            while (fileChannel.read(buffer) != -1) {
                buffer.flip();
                socketChannel.write(buffer);
                buffer.clear();
            }
            // 等待服务器处理
            Thread.sleep(1000);
            fileChannel.close();
            socketChannel.close();
            System.out.println("文件发送完成");
        }
    }
}

最佳实践建议

1 缓冲区管理

public class BufferManagementDemo {
    // 动态调整缓冲区大小
    public static ByteBuffer createOptimalBuffer(long fileSize) {
        int optimalSize = 8192;
        if (fileSize < 1024) {
            optimalSize = 1024;
        } else if (fileSize < 1024 * 1024) {
            optimalSize = 8192;
        } else if (fileSize < 1024 * 1024 * 100) {
            optimalSize = 65536;
        } else {
            optimalSize = 1048576; // 1MB for large files
        }
        return ByteBuffer.allocate(optimalSize);
    }
    // 使用直接缓冲区提高性能
    public static ByteBuffer createDirectBuffer(int size) {
        return ByteBuffer.allocateDirect(size);
    }
    // 切片和复制
    public static void bufferOperations() {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 创建切片
        ByteBuffer slice = buffer.slice();
        // 复制(只复制内容和位置,不共享数据)
        ByteBuffer duplicate = buffer.duplicate();
        // 读取指定位置数据
        buffer.position(100);
        byte[] data = new byte[50];
        buffer.get(data);
    }
}

2 异常处理和资源管理

public class NIOExceptionHandling {
    public static void safeChannelOperation() {
        FileChannel channel = null;
        try {
            channel = FileChannel.open(Paths.get("test.txt"), StandardOpenOption.READ);
            // 操作通道
        } catch (IOException e) {
            System.err.println("IO操作失败:" + e.getMessage());
            e.printStackTrace();
        } finally {
            // 确保资源关闭
            if (channel != null) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 推荐使用try-with-resources
    public static void tryWithResourcesDemo() {
        try (FileChannel channel = FileChannel.open(Paths.get("test.txt"), StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            channel.read(buffer);
        } catch (IOException e) {
            System.err.println("IO操作失败:" + e.getMessage());
        }
    }
}

性能对比

操作类型 传统IO Java NIO 适用场景
文件复制 逐字节/逐行 缓冲区+零拷贝 大文件处理
Socket通信 阻塞式 非阻塞+多路复用 高并发服务器
文件操作 大量系统调用 内存映射 文件处理
缓冲区 自动管理 手动控制 需要精细控制性能

Java NIO相比传统IO提供了:

  1. 非阻塞模式:支持高并发
  2. 缓冲区操作:更细粒度的控制
  3. Selector多路复用:单线程处理多个连接
  4. 零拷贝技术:提高大文件传输性能

在实际应用中,需要根据具体场景选择合适的方案:

  • 简单文件操作:传统IO
  • 高并发网络服务:NIO + Selector
  • 大文件处理:内存映射
  • 频繁IO操作:NIO缓冲区

抱歉,评论功能暂时关闭!