public class NioFileDemo {
    public static void main(String[] args) {
        NioFileDemo demo = new NioFileDemo();
        demo.writeFile();
        demo.readFile();
    }

    // 使用 NIO 写入文件
    public void writeFile() {
        Path path = Paths.get("logs/itwanger/paicoding.txt");
        try {
            FileChannel fileChannel = FileChannel.open(path, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));

            ByteBuffer buffer = StandardCharsets.UTF_8.encode("学编程就上技术派");
            fileChannel.write(buffer);

            System.out.println("写入完成");
            fileChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 使用 NIO 读取文件
    public void readFile() {
        Path path = Paths.get("logs/itwanger/paicoding.txt");
        try {
            FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ);
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            int bytesRead = fileChannel.read(buffer);
            while (bytesRead != -1) {
                buffer.flip();
                System.out.println("读取的内容: " + StandardCharsets.UTF_8.decode(buffer));
                buffer.clear();
                bytesRead = fileChannel.read(buffer);
            }

            fileChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class AioDemo {

    public static void main(String[] args) {
        AioDemo demo = new AioDemo();
        demo.writeFile();
        demo.readFile();
    }

    // 使用 AsynchronousFileChannel 写入文件
    public void writeFile() {
        // 使用 Paths.get() 获取文件路径
        Path path = Paths.get("logs/itwanger/paicoding.txt");
        try {
            // 用 AsynchronousFileChannel.open() 打开文件通道,指定写入和创建文件的选项。
            AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

            // 将要写入的字符串("学编程就上技术派")转换为 ByteBuffer。
            ByteBuffer buffer = StandardCharsets.UTF_8.encode("学编程就上技术派");
            // 调用 fileChannel.write() 方法将 ByteBuffer 中的内容写入文件。这是一个异步操作,因此需要使用 Future 对象等待写入操作完成。
            Future<Integer> result = fileChannel.write(buffer, 0);
            // 等待写操作完成
            result.get();

            System.out.println("写入完成");
            fileChannel.close();
        } catch (IOException | InterruptedException | java.util.concurrent.ExecutionException e) {
            e.printStackTrace();
        }
    }

    // 使用 AsynchronousFileChannel 读取文件
    public void readFile() {
        Path path = Paths.get("logs/itwanger/paicoding.txt");
        try {
            // 指定读取文件的选项。
            AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
            // 创建一个 ByteBuffer,用于存储从文件中读取的数据。
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // 调用 fileChannel.read() 方法从文件中异步读取数据。该方法接受一个 CompletionHandler 对象,用于处理异步操作完成后的回调。
            fileChannel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result, ByteBuffer attachment) {
                    // 在 CompletionHandler 的 completed() 方法中,翻转 ByteBuffer(attachment.flip()),然后使用 Charset.forName("UTF-8").decode() 将其解码为字符串并打印。最后,清空缓冲区并关闭文件通道。
                    attachment.flip();
                    System.out.println("读取的内容: " + StandardCharsets.UTF_8.decode(attachment));
                    attachment.clear();
                    try {
                        fileChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    // 如果异步读取操作失败,CompletionHandler 的 failed() 方法将被调用,打印错误信息。
                    System.out.println("读取失败");
                    exc.printStackTrace();
                }
            });

            // 等待异步操作完成
            Thread.sleep(1000);

        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

为什么使用 AIO 的代码看起来更复杂了?


非常好!你观察得很敏锐。虽然 AIO(Asynchronous I/O,异步 I/O) 理论上是比 NIO(Non-blocking I/O,非阻塞 I/O) 更先进的 I/O 模型,但在代码层面上,AIO 代码确实显得更“复杂”。我们来仔细分析为什么会这样。


💡 一、NIO vs AIO 的编程模型不同

特点NIOAIO
模型同步非阻塞(由程序主动“轮询”数据是否可读/可写)异步非阻塞(由系统回调通知程序操作完成)
控制权主程序自己控制读写时机控制权交给操作系统/回调机制
操作风格顺序执行(像普通文件读写)回调式(要注册 CompletionHandler)
理解成本简单直观逻辑分散、回调代码多、调试不便

NIO 的设计理念是“我询问系统:现在能不能读/写?”
AIO 则是“我告诉系统:帮我读/写,完成了你再通知我。”


🧩 二、代码复杂度的来源

1️⃣ 异步编程带来了回调逻辑

在 AIO 中,为了异步执行并在完成时通知,你必须提供一个 CompletionHandlerFuture 来接收结果。

这就导致:

  • 代码结构从顺序执行 → 回调嵌套式执行;
  • 错误处理逻辑需要分散编写;
  • 需要显式等待或协调线程(例如 Thread.sleep())。

举个形象的例子:

  • NIO 就像是你一直在问:“文件写完了吗?好,我继续干别的。”
  • AIO 则是你说:“写文件这件事交给你了,写完打我电话通知我。”
    但这意味着你要提供“打电话之后要干什么”的完整逻辑(CompletionHandler)。

2️⃣ 需要同时考虑 Future 和 CompletionHandler 两种写法

AIO 支持两种调用风格:

  • Future 模式(阻塞直到异步操作完成);
  • CompletionHandler 模式(回调通知结果)。

这虽然提供了灵活性,但也导致代码显得更啰嗦,因为你往往要写更多的类型声明和异常处理逻辑。


3️⃣ 为了健壮性,AIO 必须显式控制线程或延迟

异步回调是系统在后台线程中执行的,主线程可能在回调发生前就退出。因此你在示例代码中看到需要:

Thread.sleep(1000);

这一行是为了防止主线程结束,把回调线程一同“带死”;这也是初学者常觉得“麻烦”的地方。


⚙️ 三、总结:AIO 更复杂但更适用大并发场景

对比维度NIOAIO
任务模型同步轮询异步回调
代码复杂度简洁较复杂
CPU 开销较高(轮询)较低(事件通知)
适用场景少量 I/O、顺序执行任务高并发、大量 I/O、服务端网络通信
学习曲线平缓陡峭

🔍 四、一句话总结

AIO 更智能,但需要你写更多“协议”告诉系统该怎么通知你;NIO 更原始,但控制更直观。

换句话说,

AIO 复杂的是“结构”,简单的是“运行”;
NIO 简单的是“结构”,复杂的是“调度”。

确实很多人都说Java的代码看起来更啰嗦,那么如果是Go 语言实现同样的功能,代码会更简洁吗?


非常好的问题 👍

你提到了一个关键感受 —— Java 代码“啰嗦”,而 Go 语言(Golang) 常被称为“简洁、直观、高效”。我们确实可以从你给出的这个 I/O 场景中清楚地看出区别。


🧩 一、Java VS Go 的设计哲学差异

维度JavaGo
编程范式面向对象(OOP),强调类型体系和结构化面向过程 + 并发优先,追求简洁
异步模型基于回调(AIO)、Future、CompletableFuture、Reactive 等基于 goroutine,语法层级支持并发
错误处理try...catch 异常系统明确返回 error,结构更扁平
I/O 模型传统上依赖 NIO/AIO 通道模型内置 goroutine 与通道(channel),并发读写更自然
语言冗余度大量接口、泛型、类型声明小而美、编译快、自带工具链

简而言之:

Go 通过语言级并发模型,就天然带有“异步 I/O 的味道”,而不需要像 Java 一样额外编写回调逻辑。

💡 二、同样功能的示例:Go 版文件读写

下面的 Go 代码实现了写入文件 + 读取文件的功能,与 Java 的 NIO / AIO 示例完全等价。

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    writeFile()
    readFile()
}

// 写文件
func writeFile() {
    path := "logs/itwanger/paicoding.txt"

    // 确保目录存在
    os.MkdirAll("logs/itwanger", os.ModePerm)

    content := []byte("学编程就上技术派")
    err := ioutil.WriteFile(path, content, 0644)
    if err != nil {
        fmt.Println("写入失败:", err)
        return
    }
    fmt.Println("写入完成")
}

// 读文件
func readFile() {
    path := "logs/itwanger/paicoding.txt"
    content, err := ioutil.ReadFile(path)
    if err != nil {
        fmt.Println("读取失败:", err)
        return
    }
    fmt.Println("读取的内容:", string(content))
}

输出示例:

写入完成
读取的内容: 学编程就上技术派

🧠 三、比较下差异

功能点Java (NIO/AIO)Go
写文件FileChannel.open + ByteBuffer + StandardOpenOptionioutil.WriteFile()
读文件FileChannel.open + ByteBuffer + decode()ioutil.ReadFile()
异步 I/O需要 Future、CompletionHandler天生支持 goroutine
错误处理try-catch 块中抛异常if err != nil {} 明确返回
代码行数50+15 左右
心智负担较重(接口 & Channel 概念)较轻(直接明确的 I/O 操作)

⚙️ 四、如果想用异步方式(例如模拟 Java AIO)

在 Go 中你甚至只需要一行 go 关键字:

go writeFile() // 异步写
go readFile()  // 异步读

因为 每个 goroutine 本质上就是一个轻量协程,你不需要包装 Future、注册 CompletionHandler 或使用线程池,Go 运行时自动帮你管理调度。


🚀 五、简洁源于语言内核

语言特性Go 的内置支持
并发✅ goroutine、channel
错误机制✅ 明确 error 返回
文件读写ioutil / os 包统一接口
工具链go fmtgo buildgo run 一体化
语法冗余❌ 无泛型装饰、无继承树

🏁 六、总结一句话

Java 是“工程化的语言”,给你所有控制权;
Go 是“工程师的语言”,给你最直接的路径。

GO 的代码确实更简洁,但是如果是JDK 25,支持协程的Java呢?


非常棒的问题!👏
你提到 JDK 25 支持协程(虚拟线程,Virtual Threads) —— 这个是 Java 近几年最重要、最革命性的变化之一,也正是让 Java 向 Go 靠拢的关键一步。

我们来深入聊一下这个问题,看看在 “支持协程的 Java” 时代,Java 代码是否能变得和 Go 一样简洁、优雅。


☕ 一、JDK 25:协程 = 虚拟线程(Virtual Threads)

协程(Coroutine)是一种用户级线程技术,
Java 的虚拟线程(Virtual Thread) 就是对协程理念的官方落地。

✅ 基本原理

在传统 Java 中:

  • 每个线程都对应一个 操作系统级线程(OS thread)
  • 操作系统调度线程 → 成本高昂(尤其在百万并发时)。

JDK 21(正式引入)→ JDK 25(稳定优化) 之后:

  • Java 引入 Virtual Threads,由 JVM 调度,而非 OS。
  • 它们是 轻量级的线程,挂起和恢复都由 JVM 管理。
  • 语义仍然是“阻塞式”的,看起来是同步写法,但运行时是异步的。
💡一句话理解:
Java 25 的虚拟线程 = Go 的 goroutine。

🧩 二、你原来的 AIO 示例,用虚拟线程改写后是什么样?

我们先看看你之前的 AIO 示例里最“繁琐”的部分:

  • AsynchronousFileChannel
  • CompletionHandler
  • Future.get()
  • Thread.sleep() 等待异步完成

在 Java 25 中,我们可以用 普通 I/O(同步写法)+ 虚拟线程,完全消除这些复杂性。


✅ 改写示例(JDK 25 / Project Loom 风格)

import java.io.IOException;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;

public class VirtualThreadIODemo {
    public static void main(String[] args) throws Exception {
        // 启动两个虚拟线程同时读写
        Thread writer = Thread.startVirtualThread(() -> {
            try {
                writeFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        Thread reader = Thread.startVirtualThread(() -> {
            try {
                // 暂停一下,确保文件写好
                Thread.sleep(100);
                readFile();
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        });

        writer.join();
        reader.join();
    }

    static void writeFile() throws IOException {
        Path path = Paths.get("logs/itwanger/paicoding.txt");
        Files.createDirectories(path.getParent());
        Files.writeString(path, "学编程就上技术派", StandardCharsets.UTF_8);
        System.out.println("写入完成");
    }

    static void readFile() throws IOException {
        Path path = Paths.get("logs/itwanger/paicoding.txt");
        String content = Files.readString(path, StandardCharsets.UTF_8);
        System.out.println("读取的内容: " + content);
    }
}

输出示例:

写入完成
读取的内容: 学编程就上技术派

⚡ 三、对比结果:和 Go 的差距几乎消失了

特性传统 Java (AIO)新式 Java (虚拟线程)Go
并发模型Future / CompletionHandler虚拟线程(协程调度)Goroutine
写法风格回调 / 异步繁琐同步写法、异步执行同步写法、异步执行
性能操作系统线程调度JVM 用户态调度Go runtime 调度
创建开销高(MB级栈空间)极低(KB级)极低(KB级)
代码简洁度❌ 啰嗦✅ 接近 Go✅ 简洁
工程生态✅ 成熟✅ 向 Go 看齐✅ 简洁灵活

🧠 四、为什么协程让 Java 变简洁

  1. 去掉了回调地狱
    无需再写 FutureCompletionHandlerCompletableFuture
    所有异步任务都可以“看起来同步,实际上异步”。
  2. 不再需要线程池管理
    虚拟线程由 JVM 调度和复用,不会像以往那样创建几千个就 OOM。
  3. 兼容原生 API
    不需要修改原有的 Files.*Socket.* 等阻塞式 API,直接可用。
  4. 极为高效的并发
    实测百万虚拟线程只占几百 MB 内存,几乎和 Go 的 goroutine 数量级相同。

🌈 五、一句话总结

JDK 25 的虚拟线程让 Java 拥有了 Go 式的并发能力与简洁语义。

如今的 Java 终于可以做到:

  • 写起来是同步风格
  • 跑起来是异步高并发
  • 生态上仍然是 "老 Java" 的稳健性。

标签: none

评论已关闭