← 返回首页

Java NIO详解:非阻塞IO与缓冲区

📂 java ⏱ 4 min 697 words

Java NIO详解:非阻塞IO与缓冲区

概述

Java NIO(New IO)是JDK 1.4引入的非阻塞IO API。NIO提供了与标准IO不同的工作方式,支持面向缓冲区的、基于通道的IO操作,以及选择器机制。

1. 缓冲区(Buffer)

基本缓冲区操作

import java.nio.IntBuffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;

public class BufferExample {
    public static void main(String[] args) {
        // 创建IntBuffer
        IntBuffer intBuffer = IntBuffer.allocate(10);
        
        // 写入数据
        for (int i = 0; i < 5; i++) {
            intBuffer.put(i * 10);
        }
        
        System.out.println("容量: " + intBuffer.capacity());
        System.out.println("限制: " + intBuffer.limit());
        System.out.println("位置: " + intBuffer.position());
        
        // 切换到读模式
        intBuffer.flip();
        
        System.out.println("切换后位置: " + intBuffer.position());
        System.out.println("切换后限制: " + intBuffer.limit());
        
        // 读取数据
        while (intBuffer.hasRemaining()) {
            System.out.print(intBuffer.get() + " ");
        }
        System.out.println();
        
        // 清空缓冲区
        intBuffer.clear();
        System.out.println("清空后位置: " + intBuffer.position());
        System.out.println("清空后限制: " + intBuffer.limit());
    }
}

ByteBuffer操作

import java.nio.ByteBuffer;

public class ByteBufferExample {
    public static void main(String[] args) {
        // 创建ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(100);
        
        // 写入数据
        String text = "Hello, NIO!";
        buffer.put(text.getBytes());
        
        System.out.println("写入后位置: " + buffer.position());
        
        // 切换到读模式
        buffer.flip();
        
        // 读取数据
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        String result = new String(bytes);
        System.out.println("读取: " + result);
        
        // 直接缓冲区(堆外内存)
        ByteBuffer directBuffer = ByteBuffer.allocateDirect(100);
        directBuffer.put("Direct Buffer".getBytes());
        directBuffer.flip();
        
        byte[] directBytes = new byte[directBuffer.remaining()];
        directBuffer.get(directBytes);
        System.out.println("直接缓冲区: " + new String(directBytes));
    }
}

2. 通道(Channel)

文件通道

import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.ByteBuffer;

public class FileChannelExample {
    public static void main(String[] args) {
        try {
            // 写入文件
            FileChannel writeChannel = FileChannel.open(
                Paths.get("nio-write.txt"),
                StandardOpenOption.CREATE,
                StandardOpenOption.WRITE
            );
            
            ByteBuffer buffer = ByteBuffer.wrap("Hello, NIO Channel!".getBytes());
            writeChannel.write(buffer);
            writeChannel.close();
            System.out.println("文件已写入");
            
            // 读取文件
            FileChannel readChannel = FileChannel.open(
                Paths.get("nio-write.txt"),
                StandardOpenOption.READ
            );
            
            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            readChannel.read(readBuffer);
            readBuffer.flip();
            
            byte[] bytes = new byte[readBuffer.remaining()];
            readBuffer.get(bytes);
            System.out.println("读取: " + new String(bytes));
            readChannel.close();
            
            // 文件复制
            FileChannel sourceChannel = FileChannel.open(
                Paths.get("nio-write.txt"),
                StandardOpenOption.READ
            );
            FileChannel destChannel = FileChannel.open(
                Paths.get("nio-copy.txt"),
                StandardOpenOption.CREATE,
                StandardOpenOption.WRITE
            );
            
            destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
            sourceChannel.close();
            destChannel.close();
            System.out.println("文件已复制");
            
        } catch (Exception e) {
            System.out.println("异常: " + e.getMessage());
        }
    }
}

3. 选择器(Selector)

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

public class SelectorExample {
    public static void main(String[] args) {
        try {
            // 创建选择器
            Selector selector = Selector.open();
            
            // 创建服务器套接字通道
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);
            serverChannel.socket().bind(new java.net.InetSocketAddress(8080));
            
            // 注册选择器
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            
            System.out.println("服务器启动,等待连接...");
            
            // 选择就绪的通道
            while (true) {
                int readyChannels = selector.select();
                if (readyChannels == 0) continue;
                
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
                
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    
                    if (key.isAcceptable()) {
                        // 处理接受连接
                        System.out.println("接受连接");
                    } else if (key.isReadable()) {
                        // 处理读取
                        System.out.println("读取数据");
                    } else if (key.isWritable()) {
                        // 处理写入
                        System.out.println("写入数据");
                    }
                    
                    keyIterator.remove();
                }
            }
            
        } catch (Exception e) {
            System.out.println("异常: " + e.getMessage());
        }
    }
}

4. 文件NIO操作

import java.nio.file.*;
import java.nio.*;
import java.util.*;
import java.util.stream.*;

public class FileNIOExample {
    public static void main(String[] args) {
        try {
            // 读取文件
            List<String> lines = Files.readAllLines(Paths.get("test.txt"));
            System.out.println("文件内容:");
            lines.forEach(System.out::println);
            
            // 写入文件
            Files.write(Paths.get("nio-output.txt"), 
                Arrays.asList("Line 1", "Line 2", "Line 3"));
            System.out.println("文件已写入");
            
            // 使用Stream读取文件
            try (Stream<String> stream = Files.lines(Paths.get("nio-output.txt"))) {
                stream.forEach(System.out::println);
            }
            
            // 目录操作
            Path dir = Paths.get("nio-dir");
            if (!Files.exists(dir)) {
                Files.createDirectory(dir);
            }
            
            // 创建临时文件
            Path tempFile = Files.createTempFile(dir, "temp", ".txt");
            Files.write(tempFile, "临时文件内容".getBytes());
            System.out.println("临时文件: " + tempFile);
            
            // 遍历目录
            try (Stream<Path> paths = Files.walk(dir)) {
                paths.filter(Files::isRegularFile)
                     .forEach(System.out::println);
            }
            
            // 查找文件
            try (Stream<Path> paths = Files.find(dir, 5, 
                    (path, attrs) -> path.toString().endsWith(".txt"))) {
                paths.forEach(System.out::println);
            }
            
        } catch (Exception e) {
            System.out.println("异常: " + e.getMessage());
        }
    }
}

5. 内存映射文件

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

public class MemoryMappedFileExample {
    public static void main(String[] args) {
        try {
            // 写入测试文件
            Files.write(Paths.get("mapped.txt"), 
                "Hello, Memory Mapped File!".getBytes());
            
            // 内存映射文件读取
            FileChannel channel = FileChannel.open(
                Paths.get("mapped.txt"),
                StandardOpenOption.READ
            );
            
            MappedByteBuffer mappedBuffer = channel.map(
                FileChannel.MapMode.READ_ONLY,
                0,
                channel.size()
            );
            
            byte[] bytes = new byte[mappedBuffer.remaining()];
            mappedBuffer.get(bytes);
            System.out.println("内存映射读取: " + new String(bytes));
            
            channel.close();
            
        } catch (Exception e) {
            System.out.println("异常: " + e.getMessage());
        }
    }
}

6. 实际应用示例

大文件处理

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

public class LargeFileProcessor {
    public static void processLargeFile(String filePath) throws Exception {
        FileChannel channel = FileChannel.open(
            Paths.get(filePath),
            StandardOpenOption.READ
        );
        
        long size = channel.size();
        long position = 0;
        int chunkSize = 1024 * 1024;  // 1MB
        
        while (position < size) {
            long bytesToRead = Math.min(chunkSize, size - position);
            MappedByteBuffer buffer = channel.map(
                FileChannel.MapMode.READ_ONLY,
                position,
                bytesToRead
            );
            
            // 处理数据
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            System.out.println("处理数据块: " + position + " - " + (position + bytesToRead));
            
            position += bytesToRead;
        }
        
        channel.close();
    }
    
    public static void main(String[] args) {
        try {
            // 创建测试文件
            Files.write(Paths.get("large.txt"), 
                "Large file content".repeat(1000).getBytes());
            
            processLargeFile("large.txt");
        } catch (Exception e) {
            System.out.println("异常: " + e.getMessage());
        }
    }
}

7. 最佳实践

  1. 使用NIO进行大文件操作:内存映射文件适合处理大文件
  2. 使用选择器处理网络IO:非阻塞IO适合高并发场景
  3. 使用Files工具类:简化文件操作
  4. 正确管理缓冲区:注意flip()、clear()等方法的使用
  5. 处理编码问题:使用正确的字符编码

总结

Java NIO提供了非阻塞IO和内存映射文件等高级功能,适合处理大文件和高并发网络应用。掌握NIO的核心概念和使用方法,是编写高性能Java程序的重要技能。