Java NIO Channels 演示

NIO Channels 是 Java NIO 的核心组件之一,提供了比传统 I/O 更高效的数据传输方式。下面是一个完整的演示:

1. 基本通道类型演示

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;

public class NIOChannelsDemo {
    
    public static void main(String[] args) {
        System.out.println("=== Java NIO Channels 演示 ===\n");
        
        try {
            // 1. 文件通道演示
            fileChannelDemo();
            
            // 2. 缓冲区演示
            bufferDemo();
            
            // 3. 文件复制对比
            fileCopyComparison();
            
            // 4. Socket通道演示(简单示例)
            socketChannelDemo();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 文件通道操作演示
     */
    private static void fileChannelDemo() throws IOException {
        System.out.println("1. 文件通道操作演示");
        System.out.println("-".repeat(50));
        
        // 创建测试文件
        Path testFile = Paths.get("test_data.txt");
        String content = "Java NIO Channels 演示\n" +
                        "第二行数据\n" +
                        "第三行数据\n" +
                        "第四行数据\n";
        
        // 写入文件 - 使用 FileChannel
        System.out.println("写入文件内容:");
        System.out.println(content);
        
        try (FileChannel writeChannel = FileChannel.open(testFile, 
                StandardOpenOption.CREATE, 
                StandardOpenOption.WRITE, 
                StandardOpenOption.TRUNCATE_EXISTING)) {
            
            ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
            writeChannel.write(buffer);
            System.out.println("文件写入完成");
        }
        
        // 读取文件 - 使用 FileChannel
        System.out.println("\n读取文件内容:");
        try (FileChannel readChannel = FileChannel.open(testFile, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = readChannel.read(buffer);
            
            buffer.flip(); // 切换为读模式
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
            System.out.println("读取字节数: " + bytesRead);
        }
        
        // 文件位置操作
        System.out.println("\n文件位置操作:");
        try (RandomAccessFile raf = new RandomAccessFile(testFile.toFile(), "r");
             FileChannel channel = raf.getChannel()) {
            
            System.out.println("文件大小: " + channel.size() + " 字节");
            
            // 定位到第10个字节
            channel.position(10);
            ByteBuffer buffer = ByteBuffer.allocate(20);
            channel.read(buffer);
            buffer.flip();
            
            System.out.print("从位置10读取的内容: ");
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
            System.out.println();
        }
        
        // 清理测试文件
        Files.deleteIfExists(testFile);
        System.out.println();
    }
    
    /**
     * 缓冲区操作演示
     */
    private static void bufferDemo() {
        System.out.println("2. 缓冲区操作演示");
        System.out.println("-".repeat(50));
        
        // 创建ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(50);
        printBufferState("初始状态", buffer);
        
        // 写入数据
        String data = "Hello NIO!";
        buffer.put(data.getBytes());
        printBufferState("写入数据后", buffer);
        
        // 切换到读模式
        buffer.flip();
        printBufferState("调用flip()后", buffer);
        
        // 读取数据
        System.out.print("读取的数据: ");
        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
        System.out.println();
        printBufferState("读取数据后", buffer);
        
        // 清空缓冲区
        buffer.clear();
        printBufferState("调用clear()后", buffer);
        
        // 使用 wrap 创建缓冲区
        ByteBuffer wrappedBuffer = ByteBuffer.wrap("包装的数据".getBytes());
        printBufferState("包装缓冲区", wrappedBuffer);
        
        // mark 和 reset 操作
        ByteBuffer markBuffer = ByteBuffer.allocate(20);
        markBuffer.put("标记测试".getBytes());
        markBuffer.flip();
        
        markBuffer.get(); // 读取一个字节
        markBuffer.mark(); // 设置标记
        
        System.out.print("\n标记后读取: ");
        while (markBuffer.hasRemaining()) {
            System.out.print((char) markBuffer.get());
        }
        
        markBuffer.reset(); // 重置到标记位置
        System.out.print("\n重置后读取: ");
        while (markBuffer.hasRemaining()) {
            System.out.print((char) markBuffer.get());
        }
        System.out.println("\n");
    }
    
    /**
     * 文件复制性能对比
     */
    private static void fileCopyComparison() throws IOException {
        System.out.println("3. 文件复制性能对比");
        System.out.println("-".repeat(50));
        
        // 创建测试文件
        Path sourceFile = Paths.get("source_large.txt");
        Path dest1 = Paths.get("dest_traditional.txt");
        Path dest2 = Paths.get("dest_nio.txt");
        
        // 生成大文件内容
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            content.append("这是第 ").append(i).append(" 行数据。".repeat(10)).append("\n");
        }
        
        Files.write(sourceFile, content.toString().getBytes());
        System.out.println("创建测试文件大小: " + Files.size(sourceFile) + " 字节");
        
        // 传统IO复制
        long startTime = System.nanoTime();
        try (InputStream in = new FileInputStream(sourceFile.toFile());
             OutputStream out = new FileOutputStream(dest1.toFile())) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
        long traditionalTime = System.nanoTime() - startTime;
        
        // NIO复制
        startTime = System.nanoTime();
        try (FileChannel sourceChannel = FileChannel.open(sourceFile, StandardOpenOption.READ);
             FileChannel destChannel = FileChannel.open(dest2, 
                 StandardOpenOption.CREATE, 
                 StandardOpenOption.WRITE, 
                 StandardOpenOption.TRUNCATE_EXISTING)) {
            
            // 方法1: 使用transferTo (零拷贝)
            sourceChannel.transferTo(0, sourceChannel.size(), destChannel);
            
            // 方法2: 也可以使用transferFrom
            // destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
        }
        long nioTime = System.nanoTime() - startTime;
        
        System.out.println("传统IO复制时间: " + (traditionalTime / 1_000_000.0) + " ms");
        System.out.println("NIO复制时间: " + (nioTime / 1_000_000.0) + " ms");
        System.out.println("性能提升: " + 
            String.format("%.2f", (traditionalTime - nioTime) * 100.0 / traditionalTime) + "%");
        
        // 验证文件相同
        long sourceSize = Files.size(sourceFile);
        long dest1Size = Files.size(dest1);
        long dest2Size = Files.size(dest2);
        
        System.out.println("\n文件大小验证:");
        System.out.println("源文件: " + sourceSize + " 字节");
        System.out.println("传统IO复制: " + dest1Size + " 字节 " + 
            (sourceSize == dest1Size ? "✓" : "✗"));
        System.out.println("NIO复制: " + dest2Size + " 字节 " + 
            (sourceSize == dest2Size ? "✓" : "✗"));
        
        // 清理文件
        Files.deleteIfExists(sourceFile);
        Files.deleteIfExists(dest1);
        Files.deleteIfExists(dest2);
        System.out.println();
    }
    
    /**
     * Socket通道简单演示
     */
    private static void socketChannelDemo() throws IOException {
        System.out.println("4. Socket通道演示");
        System.out.println("-".repeat(50));
        
        // 注意:实际应用中需要启动服务器和客户端
        System.out.println("SocketChannel 使用示例:");
        System.out.println("""
            // 客户端代码示例
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("localhost", 8080));
            
            // 设置为非阻塞模式
            socketChannel.configureBlocking(false);
            
            // 写入数据
            ByteBuffer buffer = ByteBuffer.wrap("Hello Server".getBytes());
            while (buffer.hasRemaining()) {
                socketChannel.write(buffer);
            }
            
            // 服务器端代码示例
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            serverSocketChannel.configureBlocking(false);
            
            while (true) {
                SocketChannel clientChannel = serverSocketChannel.accept();
                if (clientChannel != null) {
                    // 处理客户端连接
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    clientChannel.read(readBuffer);
                    // ... 处理数据
                }
            }
            """);
        
        System.out.println("注意:这是一个代码示例,实际运行需要完整的服务器/客户端实现");
    }
    
    /**
     * 打印缓冲区状态
     */
    private static void printBufferState(String title, ByteBuffer buffer) {
        System.out.printf("%-20s: capacity=%d, position=%d, limit=%d, remaining=%d\n",
                title, 
                buffer.capacity(), 
                buffer.position(), 
                buffer.limit(),
                buffer.remaining());
    }
}

2. 高级通道特性演示

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;

public class AdvancedChannelFeatures {
    
    /**
     * 通道间的数据传输(零拷贝)
     */
    public static void transferBetweenChannels() throws IOException {
        System.out.println("\n=== 通道间数据传输(零拷贝)===");
        
        // 创建两个测试文件
        Path source = Paths.get("source_transfer.txt");
        Path dest = Paths.get("dest_transfer.txt");
        
        Files.write(source, "这是通过transferTo传输的数据".getBytes());
        
        try (FileChannel sourceChannel = FileChannel.open(source, StandardOpenOption.READ);
             FileChannel destChannel = FileChannel.open(dest, 
                 StandardOpenOption.CREATE, 
                 StandardOpenOption.WRITE)) {
            
            // 使用transferTo - 更高效的文件复制
            long transferred = sourceChannel.transferTo(0, sourceChannel.size(), destChannel);
            System.out.println("传输的字节数: " + transferred);
        }
        
        // 读取验证
        System.out.println("目标文件内容: " + new String(Files.readAllBytes(dest)));
        
        Files.deleteIfExists(source);
        Files.deleteIfExists(dest);
    }
    
    /**
     * 内存映射文件演示
     */
    public static void memoryMappedFileDemo() throws IOException {
        System.out.println("\n=== 内存映射文件演示 ===");
        
        Path mappedFile = Paths.get("mapped_file.txt");
        String content = "内存映射文件内容\n第二行\n第三行";
        
        // 写入文件
        Files.write(mappedFile, content.getBytes());
        
        try (FileChannel channel = FileChannel.open(mappedFile, 
                StandardOpenOption.READ, 
                StandardOpenOption.WRITE)) {
            
            // 创建内存映射
            MappedByteBuffer mappedBuffer = channel.map(
                FileChannel.MapMode.READ_WRITE, 0, channel.size());
            
            System.out.println("映射缓冲区大小: " + mappedBuffer.capacity());
            
            // 读取内容
            System.out.print("映射文件内容: ");
            while (mappedBuffer.hasRemaining()) {
                System.out.print((char) mappedBuffer.get());
            }
            System.out.println();
            
            // 修改内容(直接在内存中修改)
            mappedBuffer.position(0); // 重置位置
            mappedBuffer.put("修改后的".getBytes());
            
            // 强制同步到磁盘
            mappedBuffer.force();
            System.out.println("修改已同步到磁盘");
        }
        
        // 验证修改
        System.out.println("文件实际内容: " + new String(Files.readAllBytes(mappedFile)));
        
        Files.deleteIfExists(mappedFile);
    }
    
    /**
     * 分散和聚集IO演示
     */
    public static void scatterGatherDemo() throws IOException {
        System.out.println("\n=== 分散和聚集IO演示 ===");
        
        Path scatterFile = Paths.get("scatter_gather.txt");
        
        try (FileChannel channel = FileChannel.open(scatterFile, 
                StandardOpenOption.CREATE, 
                StandardOpenOption.WRITE, 
                StandardOpenOption.READ)) {
            
            // 聚集写入:将多个缓冲区的内容写入一个通道
            ByteBuffer header = ByteBuffer.wrap("Header: ".getBytes());
            ByteBuffer body = ByteBuffer.wrap("Body Content".getBytes());
            ByteBuffer footer = ByteBuffer.wrap("\nFooter".getBytes());
            
            ByteBuffer[] buffers = {header, body, footer};
            
            // 写入前需要确保所有缓冲区都在正确位置
            for (ByteBuffer buf : buffers) {
                buf.rewind();
            }
            
            channel.write(buffers);
            System.out.println("聚集写入完成");
            
            // 分散读取:从一个通道读取数据到多个缓冲区
            channel.position(0); // 重置文件位置
            
            ByteBuffer readHeader = ByteBuffer.allocate(8);
            ByteBuffer readBody = ByteBuffer.allocate(12);
            ByteBuffer readFooter = ByteBuffer.allocate(7);
            
            ByteBuffer[] readBuffers = {readHeader, readBody, readFooter};
            
            long bytesRead = channel.read(readBuffers);
            System.out.println("读取字节数: " + bytesRead);
            
            // 打印读取的内容
            System.out.print("分散读取内容: ");
            for (ByteBuffer buf : readBuffers) {
                buf.flip();
                while (buf.hasRemaining()) {
                    System.out.print((char) buf.get());
                }
            }
            System.out.println();
        }
        
        Files.deleteIfExists(scatterFile);
    }
    
    /**
     * 文件锁定演示
     */
    public static void fileLockingDemo() throws IOException {
        System.out.println("\n=== 文件锁定演示 ===");
        
        Path lockFile = Paths.get("lock_test.txt");
        Files.write(lockFile, "测试文件锁定".getBytes());
        
        try (FileChannel channel = FileChannel.open(lockFile, 
                StandardOpenOption.READ, 
                StandardOpenOption.WRITE)) {
            
            // 获取独占锁
            FileLock lock = channel.lock();
            System.out.println("获取文件锁: " + 
                (lock.isShared() ? "共享锁" : "独占锁"));
            System.out.println("锁的位置: " + lock.position() + 
                ", 大小: " + lock.size());
            
            // 在持有锁的情况下操作文件
            ByteBuffer buffer = ByteBuffer.wrap("\n在锁保护下写入".getBytes());
            channel.position(channel.size());
            channel.write(buffer);
            
            // 释放锁
            lock.release();
            System.out.println("文件锁已释放");
        }
        
        System.out.println("最终文件内容: " + new String(Files.readAllBytes(lockFile)));
        Files.deleteIfExists(lockFile);
    }
    
    public static void main(String[] args) throws IOException {
        System.out.println("=== Java NIO 高级特性演示 ===\n");
        
        transferBetweenChannels();
        memoryMappedFileDemo();
        scatterGatherDemo();
        fileLockingDemo();
    }
}

3. 使用示例和最佳实践

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.*;

/**
 * 实用的NIO工具类示例
 */
public class NIOUtils {
    
    /**
     * 高效的文本文件读取
     */
    public static List<String> readLines(Path filePath) throws IOException {
        List<String> lines = new ArrayList<>();
        
        try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocateDirect(8192); // 使用直接缓冲区
            StringBuilder lineBuilder = new StringBuilder();
            byte[] lineSeparator = System.lineSeparator().getBytes();
            
            while (channel.read(buffer) != -1) {
                buffer.flip();
                
                while (buffer.hasRemaining()) {
                    char c = (char) buffer.get();
                    if (c == '\n') {
                        lines.add(lineBuilder.toString());
                        lineBuilder.setLength(0);
                    } else if (c != '\r') {
                        lineBuilder.append(c);
                    }
                }
                
                buffer.compact();
            }
            
            if (lineBuilder.length() > 0) {
                lines.add(lineBuilder.toString());
            }
        }
        
        return lines;
    }
    
    /**
     * 高效的大文件复制
     */
    public static void copyLargeFile(Path source, Path destination) throws IOException {
        try (FileChannel sourceChannel = FileChannel.open(source, StandardOpenOption.READ);
             FileChannel destChannel = FileChannel.open(destination, 
                 StandardOpenOption.CREATE, 
                 StandardOpenOption.WRITE, 
                 StandardOpenOption.TRUNCATE_EXISTING)) {
            
            long position = 0;
            long size = sourceChannel.size();
            
            // 分块传输,适合超大文件
            while (position < size) {
                position += sourceChannel.transferTo(position, 64 * 1024 * 1024, destChannel);
            }
        }
    }
    
    /**
     * 使用内存映射快速搜索文件
     */
    public static long searchInFile(Path filePath, String searchText) throws IOException {
        try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
            MappedByteBuffer buffer = channel.map(
                FileChannel.MapMode.READ_ONLY, 0, channel.size());
            
            byte[] searchBytes = searchText.getBytes();
            long count = 0;
            
            for (int i = 0; i <= buffer.limit() - searchBytes.length; i++) {
                boolean match = true;
                for (int j = 0; j < searchBytes.length; j++) {
                    if (buffer.get(i + j) != searchBytes[j]) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    count++;
                }
            }
            
            return count;
        }
    }
    
    /**
     * 非阻塞文件监控(简单示例)
     */
    public static void watchFileChanges(Path directory) throws IOException {
        WatchService watchService = FileSystems.getDefault().newWatchService();
        
        directory.register(watchService, 
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_MODIFY,
            StandardWatchEventKinds.ENTRY_DELETE);
        
        System.out.println("开始监控目录: " + directory);
        
        Thread watchThread = new Thread(() -> {
            try {
                while (true) {
                    WatchKey key = watchService.take();
                    
                    for (WatchEvent<?> event : key.pollEvents()) {
                        WatchEvent.Kind<?> kind = event.kind();
                        
                        if (kind == StandardWatchEventKinds.OVERFLOW) {
                            continue;
                        }
                        
                        @SuppressWarnings("unchecked")
                        WatchEvent<Path> ev = (WatchEvent<Path>) event;
                        Path fileName = ev.context();
                        
                        System.out.printf("事件: %s, 文件: %s\n", kind.name(), fileName);
                    }
                    
                    if (!key.reset()) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        watchThread.setDaemon(true);
        watchThread.start();
    }
}

总结

NIO Channels 的主要优势:

  1. 更高的性能:使用缓冲区和通道减少系统调用
  2. 零拷贝技术transferTo()transferFrom() 方法
  3. 内存映射文件:通过 MappedByteBuffer 直接操作文件
  4. 非阻塞IO:支持异步操作
  5. 分散/聚集:高效处理多个缓冲区

使用建议:

  • 处理大文件时使用内存映射
  • 文件复制使用 transferTo()/transferFrom()
  • 需要高性能时使用直接缓冲区
  • 注意正确处理缓冲区的 flip/clear/rewind 操作

这个演示涵盖了 NIO Channels 的核心概念和实用技巧,可以根据具体需求选择合适的方法。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐