当前位置:首页 > 技术 > 正文内容

Java NIO 中的通道机制详解

访客 技术 2026年7月18日 2

通道的基本概念

在 Java NIO 框架中,通道(Channel)是数据传输的核心组件,类似于物理世界中的管道或铁轨。它本身不存储数据,而是作为连接数据源与缓冲区之间的桥梁,实现高效的数据读写操作。通道位于 java.nio.channels 包中,支持文件、网络套接字等多种 I/O 实体,并为非阻塞和多路复用 I/O 提供基础。

常见的通道实现包括:

  • FileChannel:用于对文件进行读取、写入、映射等操作。
  • DatagramChannel:支持 UDP 协议的数据报通信。
  • SocketChannel:面向 TCP 连接的客户端通道。
  • ServerSocketChannel:用于监听 TCP 连接请求的服务端通道。

获取通道的方式

Java 提供了多种途径来获取通道实例:

  1. 通过支持通道的类调用其 getChannel() 方法:
    • 本地文件操作:如 FileInputStreamFileOutputStreamRandomAccessFile
    • 网络通信类:如 SocketServerSocketDatagramSocket
  2. JDK 7 引入的 Files 工具类提供了静态方法 newByteChannel(),可直接打开一个通道。
  3. 使用 FileChannel.open() 静态方法,传入路径和打开选项(如读、写、创建)来获取通道。

文件复制示例

以下示例将 D 盘下的 test.txt 复制到 E 盘并重命名为 copy.txt

1. 使用堆内存缓冲区(非直接缓冲区)

@Test
public void copyWithHeapBuffer() throws IOException {
    FileInputStream in = new FileInputStream("D:" + File.separator + "test.txt");
    FileOutputStream out = new FileOutputStream("E:" + File.separator + "copy.txt");

    FileChannel inChannel = in.getChannel();
    FileChannel outChannel = out.getChannel();

    ByteBuffer buffer = ByteBuffer.allocate(1024); // 堆内缓冲区

    while (inChannel.read(buffer) != -1) {
        buffer.flip();           // 切换至读模式
        outChannel.write(buffer);
        buffer.clear();          // 清空以便下次读取
    }

    inChannel.close();
    outChannel.close();
    in.close();
    out.close();
}

2. 使用内存映射缓冲区(直接缓冲区)

@Test
public void copyWithMemoryMap() throws IOException {
    Path inputPath = Paths.get("D:", "test.txt");
    Path outputPath = Paths.get("E:", "copy.txt");

    FileChannel inChannel = FileChannel.open(inputPath, StandardOpenOption.READ);
    FileChannel outChannel = FileChannel.open(outputPath, 
        StandardOpenOption.WRITE, 
        StandardOpenOption.CREATE_NEW, 
        StandardOpenOption.READ);

    // 映射整个文件到内存
    MappedByteBuffer srcBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
    MappedByteBuffer dstBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());

    byte[] data = new byte[srcBuffer.remaining()];
    srcBuffer.get(data);
    dstBuffer.put(data);

    inChannel.close();
    outChannel.close();
}

3. 通道间直接传输(零拷贝优化)

@Test
public void transferBetweenChannels() throws IOException {
    try (FileChannel source = FileChannel.open(Paths.get("D:", "test.txt"), StandardOpenOption.READ);
         FileChannel target = FileChannel.open(Paths.get("E:", "copy.txt"),
             StandardOpenOption.WRITE,
             StandardOpenOption.CREATE_NEW)) {

        // 方式一:从源通道推送数据到目标通道
        // source.transferTo(0, source.size(), target);

        // 方式二:目标通道从源拉取数据(推荐)
        target.transferFrom(source, 0, source.size());
    }
}

上述方法利用操作系统级别的零拷贝机制,避免了用户空间与内核空间之间的多次数据复制,显著提升大文件处理性能。

分散读取与聚集写入

NIO 支持将单个通道的数据分散写入多个缓冲区(Scatter Reads),或将多个缓冲区的数据集中写入一个通道(Gathering Writes),适用于消息头+消息体等结构化数据处理场景。

代码演示

@Test
public void scatterAndGather() throws IOException {
    // 打开源文件进行读写
    RandomAccessFile sourceFile = new RandomAccessFile("D:" + File.separator + "testFile.txt", "rw");
    FileChannel channel = sourceFile.getChannel();

    ByteBuffer header = ByteBuffer.allocate(100);
    ByteBuffer body = ByteBuffer.allocate(1024);
    ByteBuffer[] buffers = {header, body};

    // 分散读取:按顺序填充每个缓冲区
    channel.read(buffers);

    // 切换所有缓冲区为读模式
    for (ByteBuffer buf : buffers) {
        buf.flip();
    }

    // 查看分散结果
    System.out.println("Header: " + new String(header.array(), 0, header.limit()));
    System.out.println("Body: " + new String(body.array(), 0, body.limit()));

    // 聚集写入:将多个缓冲区内容顺序写入目标通道
    RandomAccessFile destFile = new RandomAccessFile("E:" + File.separator + "copyFile.txt", "rw");
    FileChannel outputChannel = destFile.getChannel();
    outputChannel.write(buffers); // 按数组顺序写出

    channel.close();
    outputChannel.close();
}

该机制特别适合解析固定格式的二进制协议,例如先读取头部长度字段再读取具体内容。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。