四时宝库

程序员的知识宝库

java提供的IO方式(java的io和nio)

Java提供的IO方式

Java 提供了多种 IO 方式,主要分为以下三种:

1. BIO (Blocking IO): 阻塞式 IO

  • 这是最传统的一种 IO 方式,特点是同步阻塞;高并发场景下效率较低,容易导致线程资源耗尽。
  • 当线程进行 IO 操作时,会被阻塞,直到操作完成才能继续执行。
  • 适用于连接数较少且连接比较长的场景例如文件读写
  • 例子: InputStream, OutputStream, FileReader, FileWriter 等。
// 使用 BIO 读取文件内容
try (FileInputStream fis = new FileInputStream("file.txt")) {
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = fis.read(buffer)) != -1) {
        // 处理读取的数据
    }
} catch (IOException e) {
    e.printStackTrace();
}

2. NIO (Non-Blocking IO): 非阻塞式 IO

  • NIO 引入了通道 (Channel)、缓冲区 (Buffer) 和选择器 (Selector) 等概念,实现了非阻塞 IO。
  • 线程可以发起 IO 操作后立即返回,无需等待操作完成。
  • 通过 Selector 可以实现多路复用,即一个线程可以同时处理多个 Channel。
  • 适用于连接数较多且连接比较短的场景。Selector 的空轮询问题需要处理。空轮询是 NIO 编程中需要注意的问题,它会浪费 CPU 资源、增加响应延迟和能耗。 通过设置超时时间、使用非阻塞方法或事件驱动模型,可以有效地避免空轮询问题,提高应用程序的性能和效率。
  • 例子: SocketChannel, ServerSocketChannel, Selector 等。
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.Iterator;
import java.util.Set;

public class EchoServer {

    private static final int BUFFER_SIZE = 1024;

    public static void main(String[] args) throws IOException {
        // 创建 Selector 和 ServerSocketChannel
        Selector selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress("localhost", 8080));
        serverChannel.configureBlocking(false);
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 循环处理事件
        while (true) {
            // 阻塞等待事件就绪
            if (selector.select() == 0) {
                // 处理空轮询
                System.out.println("Selector 空轮询...");
                continue;
            }

            // 获取就绪 Channel 的 SelectionKey 集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();

                // 处理连接事件
                if (key.isAcceptable()) {
                    handleAccept(key);
                }

                // 处理读事件
                if (key.isReadable()) {
                    handleRead(key);
                }

                // 处理写事件
                if (key.isWritable()) {
                    handleWrite(key);
                }

                // 移除处理过的 SelectionKey
                keyIterator.remove();
            }
        }
    }

    // 处理连接事件
    private static void handleAccept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(key.selector(), SelectionKey.OP_READ);
        System.out.println("客户端连接:" + clientChannel.getRemoteAddress());
    }

    // 处理读事件
    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
        int bytesRead = clientChannel.read(buffer);

        if (bytesRead > 0) {
            buffer.flip();
            // 将读取的数据写入到另一个缓冲区,用于后续写回客户端
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytesRead);
            writeBuffer.put(buffer);
            writeBuffer.flip();
            clientChannel.register(key.selector(), SelectionKey.OP_WRITE, writeBuffer);
        } else if (bytesRead == -1) {
            // 客户端关闭连接
            clientChannel.close();
            System.out.println("客户端断开连接:" + clientChannel.getRemoteAddress());
        }
    }

    // 处理写事件
    private static void handleWrite(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        clientChannel.write(buffer);

        if (!buffer.hasRemaining()) {
            // 数据写完,重新注册读事件
            clientChannel.register(key.selector(), SelectionKey.OP_READ);
        }
    }
}

处理空轮询: 在 selector.select() 返回 0 时,表示没有事件就绪,此时进行空轮询处理。
处理连接事件: 当 key.isAcceptable() 为 true 时,表示有新的客户端连接请求,接受连接并注册读事件。
处理读事件: 当 key.isReadable() 为 true 时,表示客户端发送了数据,读取数据并将其写入到另一个缓冲区,用于后续写回客户端。同时注册写事件。
处理写事件: 当 key.isWritable() 为 true 时,表示可以向客户端发送数据,将之前读取的数据写回客户端。如果数据写完,重新注册读事件。
NIO 中处理空轮询的代码示例:
在 Netty 中,事件驱动模型由 EventLoopGroup 和 EventLoop 处理。EventLoopGroup 负责管理 EventLoop,而 EventLoop 负责处理 Channel 的 I/O 事件。当有 I/O 事件发生时,Netty 会自动调用相应的 ChannelHandler 进行处理,避免了空轮询问题。

选择合适的方法:
设置超时时间:  适用于需要定期检查其他资源或执行其他任务的场景。
使用 selectNow() 方法:  适用于需要立即响应 I/O 事件的场景。
事件驱动模型:  适用于高并发、高性能的网络应用程序。

========方法一:设置超时时间
Selector selector = Selector.open();
// ... 注册通道 ...

while (true) {
    // 设置超时时间为 1 秒
    if (selector.select(1000) == 0) {
        // 处理空轮询
        System.out.println("Selector 空轮询...");
        // 可以执行一些其他的任务,例如检查其他资源
        continue;
    }

    // ... 处理就绪通道 ...
}

=======方法二:使用 selectNow() 方法
Selector selector = Selector.open();
// ... 注册通道 ...

while (true) {
    // 使用 selectNow() 立即返回,即使没有通道就绪
    if (selector.selectNow() == 0) {
        // 处理空轮询
        System.out.println("Selector 空轮询...");
        // 可以执行一些其他的任务,例如检查其他资源
    } else {
        // ... 处理就绪通道 ...
    }
}

=======方法三:事件驱动模型(Netty 示例)
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class)
         .childHandler(new ChannelInitializer<SocketChannel>() {
             @Override
             protected void initChannel(SocketChannel ch) {
                 // ... 添加 ChannelHandler ...
             }
         });

// 绑定端口并启动服务器
ChannelFuture future = bootstrap.bind(8080).sync();
future.channel().closeFuture().sync();

3. AIO (Asynchronous IO): 异步 IO

  • AIO 是 NIO 的升级版,实现了真正的异步 IO。
  • 线程发起 IO 操作后可以立即返回,并注册一个回调函数,当操作完成后会自动调用回调函数。
  • 适用于连接数非常多且连接比较长的场景。并非所有操作系统都支持 AIO。
  • 例子: AsynchronousSocketChannel, AsynchronousServerSocketChannel 等。
// 使用 AIO 读取文件内容
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(Paths.get("file.txt"));
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> result = fileChannel.read(buffer, 0);

// 获取读取结果
int bytesRead = result.get();
// 处理读取的数据

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接