Java笔记——IO

在 Java 的世界里,I/O(输入/输出)是一个绕不开的话题。无论是文件读写、网络通信,还是与外部系统的交互,都离不开 I/O 操作。Java 的 I/O 体系经历了从传统的 BIO(Blocking I/O)到 NIO(Non-blocking I/O),再到 AIO(Asynchronous I/O)的演进。本文将带你全面了解 Java I/O 的核心概念、使用方式和背后的设计思想。

一、Java I/O 概述

Java 的 I/O 主要分为两大类:字节流字符流,分别用于处理二进制数据和文本数据。每个大类又细分为输入流和输出流。

1.1 字节流

  • InputStream:所有字节输入流的父类,抽象了从数据源读取字节的能力。

  • OutputStream:所有字节输出流的父类,抽象了向目的地写入字节的能力。

1.2 字符流

  • Reader:所有字符输入流的父类,用于读取字符数据(自动处理字符编码)。

  • Writer:所有字符输出流的父类,用于写入字符数据。

1.3 装饰器模式

Java I/O 大量运用了装饰器模式,通过包装基础流来添加新功能(如缓冲、压缩、对象序列化等)。例如:

java 复制代码
// 用缓冲流包装文件流,提高读写效率
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("data.txt"));

这种设计让开发者可以灵活组合,但也带来了类的数量繁多、层次复杂的问题。

二、BIO -- 传统的阻塞 I/O

BIO(Blocking I/O)是 Java 1.4 之前唯一的 I/O 模型。它的特点是:每次 I/O 操作(读或写)都会阻塞当前线程,直到数据就绪或操作完成

2.1 简单文件读写示例

java 复制代码
import java.io.*;

public class BioFileDemo {
    public static void main(String[] args) {
        // 写入文件
        try (FileOutputStream fos = new FileOutputStream("example.txt");
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            String content = "Hello, Java I/O!";
            bos.write(content.getBytes());
            System.out.println("写入完成");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 读取文件
        try (FileInputStream fis = new FileInputStream("example.txt");
             BufferedInputStream bis = new BufferedInputStream(fis)) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = bis.read(buffer)) != -1) {
                System.out.println(new String(buffer, 0, length));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.2 BIO 的局限

BIO 模型简单直观,但存在一个致命问题:每个连接都需要一个独立的线程 。在传统的网络编程中,服务端 accept() 后,会为每个客户端创建一个线程处理请求。当并发连接数很高时,系统会创建大量线程,导致线程切换开销大、内存占用高,甚至资源耗尽。

三、NIO -- 非阻塞 I/O

Java 1.4 引入了 NIO(New I/O),提供了非阻塞多路复用 的能力。NIO 的核心组件是 ChannelBufferSelector

  • Channel:类似传统流,但可以异步读写,支持双向传输(既可读也可写)。

  • Buffer:数据容器,所有读写操作都通过 Buffer 完成。

  • Selector:单线程管理多个 Channel,通过事件驱动机制(如读就绪、写就绪)实现高并发。

3.1 NIO 文件拷贝示例

java 复制代码
import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class NioFileDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("source.txt");
        FileOutputStream fos = new FileOutputStream("dest.txt");

        FileChannel readChannel = fis.getChannel();
        FileChannel writeChannel = fos.getChannel();

        // 直接使用 transferTo 实现零拷贝(操作系统级别)
        readChannel.transferTo(0, readChannel.size(), writeChannel);

        readChannel.close();
        writeChannel.close();
        fis.close();
        fos.close();
    }
}

3.2 基于 Selector 的网络服务器模型(简化)

java 复制代码
import java.net.*;
import java.nio.*;
import java.nio.channels.*;

public class NioServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.bind(new InetSocketAddress(8080));
        serverSocket.configureBlocking(false); // 非阻塞
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select(); // 阻塞直到有事件就绪
            for (SelectionKey key : selector.selectedKeys()) {
                if (key.isAcceptable()) {
                    SocketChannel client = serverSocket.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int read = client.read(buffer);
                    if (read == -1) {
                        client.close();
                        continue;
                    }
                    buffer.flip();
                    // 处理数据...
                }
                selector.selectedKeys().clear();
            }
        }
    }
}

NIO 的优势:单个线程可以同时处理成千上万个连接,避免了频繁创建线程的开销。基于事件驱动,资源利用率高。

四、AIO -- 异步 I/O

Java 1.7 引入了 AIO(Asynchronous I/O),也称为 NIO.2。AIO 是真正意义上的异步 I/O:当发起一个 I/O 操作后,线程立即返回,待操作完成后,系统通过回调通知应用程序

AIO 使用 AsynchronousChannel 接口,如 AsynchronousFileChannelAsynchronousSocketChannel 等。

4.1 AIO 文件读取示例

java 复制代码
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.concurrent.*;

public class AioFileDemo {
    public static void main(String[] args) throws Exception {
        Path file = Paths.get("example.txt");
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(file, StandardOpenOption.READ);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 发起异步读取,通过 CompletionHandler 回调
        channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                attachment.flip();
                System.out.println("读取内容:" + new String(attachment.array(), 0, result));
                try { channel.close(); } catch (Exception e) { e.printStackTrace(); }
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                System.err.println("读取失败:" + exc.getMessage());
                try { channel.close(); } catch (Exception e) { e.printStackTrace(); }
            }
        });

        // 主线程可以继续做其他事情
        Thread.sleep(2000); // 等待回调完成,实际开发中可用 CountDownLatch
    }
}

4.2 AIO 网络通信(简单示例)

java 复制代码
import java.net.*;
import java.nio.*;
import java.nio.channels.*;

public class AioServer {
    public static void main(String[] args) throws Exception {
        AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
        server.bind(new InetSocketAddress(8080));
        server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Void attachment) {
                // 继续接收下一个连接
                server.accept(null, this);
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result, ByteBuffer attachment) {
                        attachment.flip();
                        System.out.println("收到数据:" + new String(attachment.array(), 0, result));
                        try { client.close(); } catch (Exception e) { e.printStackTrace(); }
                    }
                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                        exc.printStackTrace();
                    }
                });
            }
            @Override
            public void failed(Throwable exc, Void attachment) {
                exc.printStackTrace();
            }
        });

        Thread.sleep(30000); // 保持服务运行
    }
}

AIO 的优势:彻底解放线程,I/O 操作完全由操作系统异步完成,适合高并发、大文件等场景。但复杂度较高,目前实际应用中不如 NIO 普及(如 Netty 基于 NIO)。

五、性能对比与适用场景

I/O 模型 适用场景 优点 缺点
BIO 连接数少且固定的场景(如管理后台、简单服务) 编程简单,易于理解 每个连接一个线程,无法支撑高并发
NIO 高并发网络服务(如聊天服务器、网关) 单线程管理多连接,资源占用少 编程复杂,需要处理事件、缓冲区管理等
AIO 大文件读写、高并发且 I/O 操作较重的场景 完全异步,调用者无需等待 编程复杂度高,回调地狱,成熟框架较少

六、总结与展望

Java I/O 的演进体现了对性能的不断追求:

  • BIO 简单可靠,适合小规模应用;

  • NIO 通过多路复用实现了高并发,成为现代高性能服务器的基石(如 Netty、Tomcat NIO 模式);

  • AIO 进一步将异步能力交给操作系统,但在实际应用中尚未完全取代 NIO。

对于大多数开发者来说,理解 NIO 的原理已经足够应对日常开发。如果想深入,可以研究 Netty 框架,它封装了 NIO 的复杂性,提供了优雅的 API 和稳定的性能。

相关推荐
你不是我我2 小时前
【Java 开发日记】我们来说一下 b+ 树与 b 树的区别
java·开发语言
左左右右左右摇晃2 小时前
Java笔记——反射
java·tomcat
2501_924952692 小时前
C++中的过滤器模式
开发语言·c++·算法
萍萍学习2 小时前
蓝桥杯JAVA-3
java·职场和发展·蓝桥杯
2401_873204652 小时前
C++中的组合模式实战
开发语言·c++·算法
twc8292 小时前
Query 改写 大模型测试的数据倍增器
开发语言·人工智能·python·rag·大模型测试
西野.xuan2 小时前
内存布局(堆vs栈)一篇详解!!
java·数据结构·算法
无心水2 小时前
时间处理工程落地指南:数据库/日志/API/定时任务
java·大数据·数据库·日志·分布式架构·utc·gmt
Byron__2 小时前
HashSet/LinkedHashSet/TreeSet 原理深度解析
java·开发语言