Skip to content

NIO

NIO概述

NIO(New IO)是Java 1.4引入的非阻塞IO,提供了更高效的IO操作。

NIO vs IO

特性IONIO
方式面向流面向缓冲区
阻塞阻塞IO非阻塞IO
选择器Selector支持

Buffer

Buffer类型

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

Buffer核心属性

java
public class BufferDemo {
    public static void main(String[] args) {
        // 创建Buffer
        ByteBuffer buffer = ByteBuffer.allocate(10);
        
        System.out.println("初始状态:");
        System.out.println("capacity: " + buffer.capacity());  // 10
        System.out.println("limit: " + buffer.limit());        // 10
        System.out.println("position: " + buffer.position());  // 0
        
        // 写入数据
        buffer.put((byte) 'H');
        buffer.put((byte) 'e');
        buffer.put((byte) 'l');
        buffer.put((byte) 'l');
        buffer.put((byte) 'o');
        
        System.out.println("\n写入数据后:");
        System.out.println("position: " + buffer.position());  // 5
        
        // 切换为读模式
        buffer.flip();
        
        System.out.println("\nflip后:");
        System.out.println("limit: " + buffer.limit());        // 5
        System.out.println("position: " + buffer.position());  // 0
        
        // 读取数据
        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
        
        System.out.println("\n\n读取后:");
        System.out.println("position: " + buffer.position());  // 5
        
        // 重置position
        buffer.rewind();
        System.out.println("rewind后 position: " + buffer.position());  // 0
        
        // 清空buffer
        buffer.clear();
        System.out.println("clear后 position: " + buffer.position());   // 0
    }
}

Buffer方法

方法说明
allocate(int capacity)分配缓冲区
put()写入数据
get()读取数据
flip()切换为读模式
rewind()重置position为0
clear()清空缓冲区
compact()压缩缓冲区
mark()标记当前位置
reset()恢复到mark位置

直接缓冲区

java
// 非直接缓冲区(堆内存)
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);

// 直接缓冲区(操作系统内存)
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

Channel

FileChannel

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

public class FileChannelDemo {
    public static void main(String[] args) {
        // 写入文件
        try (FileChannel channel = new FileOutputStream("test.txt").getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.put("Hello, NIO!".getBytes());
            buffer.flip();
            channel.write(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 读取文件
        try (FileChannel channel = new FileInputStream("test.txt").getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = channel.read(buffer);
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 复制文件
        try (
            FileChannel srcChannel = new FileInputStream("source.txt").getChannel();
            FileChannel destChannel = new FileOutputStream("target.txt").getChannel()
        ) {
            // 方式1:使用transferTo
            srcChannel.transferTo(0, srcChannel.size(), destChannel);
            
            // 方式2:使用transferFrom
            // destChannel.transferFrom(srcChannel, 0, srcChannel.size());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FileChannel读写文件

java
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class RandomAccessDemo {
    public static void main(String[] args) {
        try (RandomAccessFile file = new RandomAccessFile("test.txt", "rw");
             FileChannel channel = file.getChannel()) {
            
            // 写入数据
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            String data = "Hello, FileChannel!";
            buffer.put(data.getBytes());
            buffer.flip();
            channel.write(buffer);
            
            // 读取数据
            buffer.clear();
            channel.position(0);  // 移动到文件开头
            int bytesRead = channel.read(buffer);
            buffer.flip();
            System.out.println(new String(buffer.array(), 0, bytesRead));
            
            // 获取文件大小
            long fileSize = channel.size();
            System.out.println("文件大小: " + fileSize);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Selector

Selector用于实现非阻塞IO的多路复用。

服务端示例

java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NioServer {
    public static void main(String[] args) throws IOException {
        // 创建Selector
        Selector selector = Selector.open();
        
        // 创建ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        serverChannel.configureBlocking(false);  // 非阻塞模式
        
        // 注册到Selector,监听连接事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        System.out.println("服务器启动,监听端口8080...");
        
        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());
                    
                } else if (key.isReadable()) {
                    // 处理读取
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = client.read(buffer);
                    
                    if (bytesRead == -1) {
                        client.close();
                        System.out.println("客户端断开");
                    } else {
                        buffer.flip();
                        String message = new String(buffer.array(), 0, bytesRead);
                        System.out.println("收到消息: " + message);
                        
                        // 回显
                        buffer.rewind();
                        client.write(buffer);
                    }
                }
            }
        }
    }
}

客户端示例

java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NioClient {
    public static void main(String[] args) throws IOException {
        SocketChannel channel = SocketChannel.open();
        channel.configureBlocking(false);
        channel.connect(new InetSocketAddress("localhost", 8080));
        
        // 等待连接完成
        while (!channel.finishConnect()) {
            System.out.println("连接中...");
        }
        
        System.out.println("连接成功");
        
        Scanner scanner = new Scanner(System.in);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        while (true) {
            System.out.print("输入消息: ");
            String message = scanner.nextLine();
            
            if ("exit".equals(message)) {
                break;
            }
            
            // 发送消息
            buffer.clear();
            buffer.put(message.getBytes());
            buffer.flip();
            channel.write(buffer);
            
            // 接收响应
            buffer.clear();
            int bytesRead = channel.read(buffer);
            if (bytesRead > 0) {
                buffer.flip();
                String response = new String(buffer.array(), 0, bytesRead);
                System.out.println("服务器响应: " + response);
            }
        }
        
        channel.close();
    }
}

Path和Files

Path类(Java 7+)

java
import java.nio.file.*;

public class PathDemo {
    public static void main(String[] args) {
        // 创建Path
        Path path = Paths.get("test.txt");
        Path path2 = Paths.get("dir", "subdir", "file.txt");
        Path path3 = Path.of("test.txt");  // Java 11+
        
        // Path信息
        System.out.println("文件名: " + path.getFileName());
        System.out.println("父目录: " + path.getParent());
        System.out.println("根目录: " + path.getRoot());
        System.out.println("绝对路径: " + path.toAbsolutePath());
        
        // 路径操作
        Path normalized = Paths.get("dir/../file.txt").normalize();
        System.out.println("规范化: " + normalized);
        
        Path resolved = Paths.get("dir").resolve("file.txt");
        System.out.println("拼接: " + resolved);
        
        Path relative = Paths.get("dir/subdir/file.txt").relativize(Paths.get("dir/other.txt"));
        System.out.println("相对路径: " + relative);
    }
}

Files类

java
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class FilesDemo {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("test.txt");
        
        // 创建文件
        Files.createFile(path);
        
        // 创建目录
        Files.createDirectories(Paths.get("dir/subdir"));
        
        // 写入文件
        Files.writeString(path, "Hello, Files!", StandardCharsets.UTF_8);
        
        // 读取文件
        String content = Files.readString(path);
        System.out.println(content);
        
        // 读取所有行
        List<String> lines = Files.readAllLines(path);
        lines.forEach(System.out::println);
        
        // 读取所有字节
        byte[] bytes = Files.readAllBytes(path);
        
        // 复制文件
        Files.copy(path, Paths.get("copy.txt"), StandardCopyOption.REPLACE_EXISTING);
        
        // 移动文件
        Files.move(path, Paths.get("moved.txt"), StandardCopyOption.REPLACE_EXISTING);
        
        // 删除文件
        Files.deleteIfExists(Paths.get("moved.txt"));
        
        // 文件信息
        System.out.println("大小: " + Files.size(path));
        System.out.println("是否文件: " + Files.isRegularFile(path));
        System.out.println("是否目录: " + Files.isDirectory(path));
        System.out.println("是否隐藏: " + Files.isHidden(path));
        
        // 遍历目录
        Files.walk(Paths.get("."))
             .filter(Files::isRegularFile)
             .forEach(System.out::println);
        
        // 查找文件
        Files.find(Paths.get("."), 10, 
            (p, attr) -> p.toString().endsWith(".java"))
             .forEach(System.out::println);
    }
}

NIO vs IO选择

场景推荐
少量连接传统IO
大量连接NIO + Selector
文件复制FileChannel.transferTo
文件读写Files类(Java 7+)
高性能网络Netty框架