java基础——java io与nio:从基础到实践

目录

一、引言

二、Java IO 流分类详解

1. 字节流(Byte Stream)

1.1.FileInputStream

1.2.FileOutputStream

1.3.BufferedInputStream

1.4.BufferedOutputStream

1.5. ObjectInputStream / ObjectOutputStream

2.字符流(Character Stream)

2.1.FileReader

2.2.FileWriter

2.3.BufferedReader

2.4.BufferedWriter

2.5.InputStreamReader

2.6.OutputStreamWriter

三、常见的五种 I/O 模型及实现

 1.阻塞 I/O(Blocking I/O)

2.非阻塞 I/O(Non-blocking I/O)

3.多路复用 I/O(I/O Multiplexing)

4.信号驱动 I/O(Signal-driven I/O)

5. 异步 I/O(Asynchronous I/O)

四、Java NIO详解

1.NIO的核心组件

1.1.Buffer(缓冲区)

1.1.1.Buffer 的基本结构

1.1.2.Buffer 的常用类型

1.1.3.Buffer 使用步骤

1.2.Channel(通道)

1.2.1.Channel 与 Stream 的区别

1.2.2.常见 Channel 实现类

1.3.Selector(选择器)

1.3.1.Selector 的基本流程

1.4.NIO 的优势

1.5.适用场景

五、结语 


一、引言

java 提供了丰富的 I/O 支持,用于处理文件读写、网络通信等场景。理解 Java IO 和 NIO 的基本概念、流的分类以及不同 I/O 模型的工作机制,对于构建高性能、高并发的应用至关重要。
本文将从 Java IO 的基础流分类开始,介绍字节流和字符流的基本类及其使用方法;接着深入讲解常见的五种 I/O 模型在 Java 中的实现方式和适用场景,并提供简单易懂的代码示例,帮助你建立完整的知识体系。

以下是一个展示Java中io流的思维导图:

二、Java IO 流分类详解

List接口继承自Collection接口,提供了额外的功能来处理索引位置上的元素。与Set、Map不同,List允许包含重复的元素,并且可以通过索引来访问或修改特定位置的元素。

1. 字节流(Byte Stream)

用于处理二进制数据,以字节为单位进行读写操作。所有字节流都继承自 InputStream 和 OutputStream。

常见子类:

FileInputStream / FileOutputStream:用于文件的字节读写。

BufferedInputStream / BufferedOutputStream:带缓冲区的字节流,提高效率。

ObjectInputStream / ObjectOutputStream:支持对象序列化与反序列化。

1.1.FileInputStream
// 用于从文件中读取字节数据
try (FileInputStream fis = new FileInputStream("input.txt")) {
    int data;
    while ((data = fis.read()) != -1) { // 每次读取一个字节
        System.out.print((char) data); // 转换为字符输出
    }
} catch (IOException e) {
    e.printStackTrace();
}
1.2.FileOutputStream
// 用于向文件中写入字节数据
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
    String content = "Hello, FileOutputStream!";
    fos.write(content.getBytes()); // 写入字符串的字节形式
} catch (IOException e) {
    e.printStackTrace();
}
1.3.BufferedInputStream
// 带缓冲区的输入流,提高读取效率
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.txt"))) {
    int data;
    while ((data = bis.read()) != -1) {
        System.out.print((char) data);
    }
} catch (IOException e) {
    e.printStackTrace();
}

实现方式:一次性从底层输入流读取一块数据到内存缓冲区中,后续的 read() 调用直接从缓冲区中获取数据,直到缓冲区耗尽再重新加载。

1.4.BufferedOutputStream
// 带缓冲区的输出流,提高写入效率
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("buffered_output.txt"))) {
    String content = "This is a buffered output example.";
    bos.write(content.getBytes());
} catch (IOException e) {
    e.printStackTrace();
}
1.5. ObjectInputStream / ObjectOutputStream
// 序列化与反序列化对象
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
    Person person = new Person("Alice", 25);
    oos.writeObject(person); // 写入对象
} catch (IOException e) {
    e.printStackTrace();
}

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
    Person loadedPerson = (Person) ois.readObject(); // 读取对象
    System.out.println(loadedPerson);
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}
2.字符流(Character Stream)
2.1.FileReader
// 用于读取文本文件中的字符
try (FileReader reader = new FileReader("input.txt")) {
    int ch;
    while ((ch = reader.read()) != -1) {
        System.out.print((char) ch);
    }
} catch (IOException e) {
    e.printStackTrace();
}
2.2.FileWriter
// 用于向文本文件写入字符内容
try (FileWriter writer = new FileWriter("output.txt")) {
    writer.write("Hello, FileWriter!"); // 写入字符串
} catch (IOException e) {
    e.printStackTrace();
}
2.3.BufferedReader
// 高效读取文本行,支持按行读取
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) {
    String line;
    while ((line = br.readLine()) != null) { // 按行读取
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}
2.4.BufferedWriter
// 高效写入文本内容,支持换行
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
    bw.write("First line");
    bw.newLine(); // 换行
    bw.write("Second line");
} catch (IOException e) {
    e.printStackTrace();
}

2.5.InputStreamReader
// 将字节流转换为字符流,可指定编码格式
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("input.txt"), "UTF-8")) {
    int ch;
    while ((ch = isr.read()) != -1) {
        System.out.print((char) ch);
    }
} catch (IOException e) {
    e.printStackTrace();
}
2.6.OutputStreamWriter
// 将字符流转换为字节流,可指定编码格式
try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8")) {
    osw.write("Hello, OutputStreamWriter with UTF-8 encoding.");
} catch (IOException e) {
    e.printStackTrace();
}

三、常见的五种 I/O 模型及实现

 1.阻塞 I/O(Blocking I/O)

线程发起 I/O 请求后会一直等待,直到数据准备好并完成复制。

适用场景:
适用于连接数少、请求简单的应用,如小型局域网服务。

示例代码

使用 ServerSocket 和 Socket 编写的 BIO 服务器

        // 创建服务器端Socket,绑定到8080端口
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("阻塞IO服务器启动,等待客户端连接...");

        while (true) {
            // accept() 是一个阻塞方法,直到有客户端连接才会继续执行
            Socket socket = serverSocket.accept();
            System.out.println("客户端已连接");

            // 处理客户端请求(单线程处理)
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = reader.readLine(); // readLine() 也是阻塞方法
            System.out.println("收到消息:" + line);

            // 向客户端发送响应
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            writer.write("Hello from server\n");
            writer.flush();

            // 关闭资源
            socket.close();
        }
2.非阻塞 I/O(Non-blocking I/O)

I/O 操作不会阻塞线程,立即返回结果,适合需要控制线程行为的场景。

适用场景:

常用于底层网络库或需手动控制轮询的场景。

示例代码:

使用 NIO 的 SocketChannel 和非阻塞模式

        // 打开客户端Socket通道
        SocketChannel socketChannel = SocketChannel.open();
        // 设置为非阻塞模式
        socketChannel.configureBlocking(false);
        // 尝试连接服务器
        boolean connected = socketChannel.connect(new InetSocketAddress("localhost", 8080));

        if (connected) {
            System.out.println("已连接服务器");
        } else {
            System.out.println("未立即连接成功,可能正在建立连接...");
        }

        // 缓冲区用于读写数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 检查是否连接完成
        while (!socketChannel.finishConnect()) {
            System.out.println("等待连接完成...");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 发送数据给服务端
        String message = "Hello, Non-blocking IO!";
        buffer.put(message.getBytes());
        buffer.flip(); // 切换为读模式
        socketChannel.write(buffer); // 写入数据
        System.out.println("已发送消息");

        // 清空缓冲区准备接收响应
        buffer.clear();

        // 尝试读取响应(非阻塞,如果没数据会返回0)
        int bytesRead = socketChannel.read(buffer);
        if (bytesRead > 0) {
            buffer.flip(); // 准备读取内容
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            System.out.println("收到响应:" + new String(data));
        } else {
            System.out.println("暂无响应数据");
        }

        // 关闭连接
        socketChannel.close();
3.多路复用 I/O(I/O Multiplexing)

通过 Selector 监控多个通道的状态变化,实现单线程管理多个连接。

适用场景:

高并发服务器,如 Web 服务器、聊天服务等。

示例代码:

使用 Selector + SocketChannel/ServerSocketChannel

        // 打开选择器
        Selector selector = Selector.open();

        // 打开服务器Socket通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress("localhost", 8080));
        serverSocketChannel.configureBlocking(false); // 设置为非阻塞
        // 注册到选择器,监听 OP_ACCEPT 事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("多路复用IO服务器启动,等待事件...");

        while (true) {
            // 阻塞等待事件发生(可设置超时时间)
            int readyCount = selector.select();
            if (readyCount == 0) continue;

            // 获取所有就绪事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

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

                // 处理连接事件
                if (key.isAcceptable()) {
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    SocketChannel clientChannel = serverChannel.accept(); // 接受新连接
                    clientChannel.configureBlocking(false); // 设置为非阻塞
                    // 注册读事件
                    clientChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端连接加入");
                }

                // 处理读事件
                if (key.isReadable()) {
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = clientChannel.read(buffer); // 非阻塞读取

                    if (bytesRead > 0) {
                        buffer.flip(); // 切换为读模式
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        System.out.println("收到数据:" + new String(data));

                        // 回应客户端
                        ByteBuffer responseBuffer = ByteBuffer.wrap("Echo: ".getBytes());
                        ByteBuffer combined = ByteBuffer.allocate(responseBuffer.remaining() + buffer.remaining());
                        combined.put(responseBuffer);
                        combined.put(buffer);
                        combined.flip();
                        clientChannel.write(combined); // 发送响应
                    } else if (bytesRead == -1) {
                        // 客户端断开连接
                        System.out.println("客户端断开");
                        clientChannel.close();
                    }
                }

                // 移除当前事件,避免重复处理
                iterator.remove();
            }
        }
4.信号驱动 I/O(Signal-driven I/O)

通过注册信号处理器,在数据就绪时由内核通知进程

Java 标准库不直接支持该模型,通常依赖操作系统级编程(如 Linux 的 SIGIO)。

5. 异步 I/O(Asynchronous I/O)

整个 I/O 操作(包括数据拷贝)都是异步完成的,线程无需等待。

使用场景:

高性能、大规模并发服务器,如数据库连接池、消息中间件。

示例代码:

使用 AsynchronousSocketChannel 和 CompletionHandler。

        // 打开异步Socket通道
        AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open();

        // 连接服务器
        clientChannel.connect(new InetSocketAddress("localhost", 8080), null, new CompletionHandler<Void, Object>() {
            @Override
            public void completed(Void result, Object attachment) {
                System.out.println("连接建立成功");

                // 发送数据
                String message = "Hello, Asynchronous IO!";
                ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
                clientChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result, ByteBuffer buffer) {
                        if (buffer.hasRemaining()) {
                            // 继续发送剩余数据
                            clientChannel.write(buffer, buffer, this);
                        } else {
                            System.out.println("数据发送完成");

                            // 准备接收响应
                            ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
                            clientChannel.read(responseBuffer, responseBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                                @Override
                                public void completed(Integer bytesRead, ByteBuffer attachment) {
                                    attachment.flip(); // 切换为读模式
                                    byte[] data = new byte[attachment.remaining()];
                                    attachment.get(data);
                                    System.out.println("收到响应:" + new String(data));
                                }

                                @Override
                                public void failed(Throwable exc, ByteBuffer attachment) {
                                    exc.printStackTrace();
                                }
                            });
                        }
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                        exc.printStackTrace();
                    }
                });
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });

        // 保持主线程运行以等待异步操作完成
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

四、Java NIO详解

Java NIO(New Input/Output)是 Java 1.4 引入的一组新的 I/O API,相较于传统的 Java IO(即 BIO),它提供了更高效、更灵活的 I/O 操作方式,尤其适合高并发、高性能场景。

1.NIO的核心组件
1.1.Buffer(缓冲区)

数据读写的目的地和来源,所有数据必须通过 Buffer 进行操作。

1.1.1.Buffer 的基本结构

Buffer 是一个容器对象,内部维护了一个数组(如 byte[]),并记录了以下关键属性:

capacity:容量,表示 Buffer 最多能容纳的数据量。

position:当前读写位置。

limit:表示可操作数据的上限(从 position 到 limit 之间为有效数据)。

mark:标记位置,可以通过 reset() 回到该位置。

1.1.2.Buffer 的常用类型

1.1.3.Buffer 使用步骤
// 1. 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配1024字节的缓冲区

// 2. 写入数据
buffer.put("Hello NIO!".getBytes());

// 3. 切换为读模式
buffer.flip();

// 4. 读取数据
while (buffer.hasRemaining()) {
    System.out.print((char) buffer.get());
}

// 5. 清空缓冲区(或 compact)
buffer.clear(); // 或 buffer.compact();

flip():切换为读模式,将 position 设为0,limit 设为之前写入的位置。
clear():清空整个缓冲区,position=0,limit=capacity。
compact():保留未读完的数据,后续写入不会覆盖。

1.2.Channel(通道)

类似于流,但可以同时进行读写,并支持异步操作。

1.2.1.Channel 与 Stream 的区别

1.2.2.常见 Channel 实现类

代码示例:

        try (FileInputStream fis = new FileInputStream("input.txt");
             FileChannel channel = fis.getChannel()) {

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = channel.read(buffer);

            while (bytesRead != -1) {
                buffer.flip(); // 切换为读模式
                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }
                buffer.clear(); // 清空准备下一次读取
                bytesRead = channel.read(buffer);
            }
        }
1.3.Selector(选择器)

多路复用机制,用于监听多个 Channel 的事件(如连接、读就绪等)。

1.3.1.Selector 的基本流程

      1.打开 Selector。
      2.将 Channel 注册到 Selector 上,并指定感兴趣的事件(如 OP_READ、OP_WRITE 等)。
      3.调用 select() 方法等待事件发生。
      4.获取就绪事件集合,处理事件。
      5.移除已处理的事件。

代码示例:同三.3多路复用IO。

1.4.NIO 的优势

非阻塞 I/O:避免线程阻塞在 I/O 操作上。
多路复用:一个线程可管理多个 Channel。
缓冲区机制:提高数据传输效率。
支持异步 I/O(AIO):适用于大规模并发。

1.5.适用场景

五、结语 

通过对Java IO和NIO的学习,我们可以根据不同的应用场景选择合适的技术方案。对于简单的文件操作或者低并发需求的服务端来说,使用标准的IO就足够了;而对于需要处理大量并发连接的高性能服务端,则应该考虑采用NIO技术。希望这篇文章能帮助初学者和有一定经验的Java开发者更深入地理解这两种重要的I/O处理机制,并能够在实际项目中灵活运用它们。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值