NIO知识点

news2025/6/6 6:27:42

一、Java NIO 基础概念

Java NIO(New Input/Output)是从 Java 1.4 版本开始引入的新的 IO API,它提供了与标准 IO 不同的工作方式。主要特点包括:

  1. 面向缓冲区:数据读取到一个稍后处理的缓冲区,需要时可在缓冲区中前后移动,增加了处理过程中的灵活性。
  2. 非阻塞 IO:允许线程在等待数据时执行其他任务,提高了系统的吞吐量和响应性。
  3. 选择器:一个选择器可以监听多个通道的事件(如连接打开、数据到达等),从而让一个线程可以处理多个通道。

二、核心组件详解

1. 缓冲区(Buffer)

缓冲区是一个用于存储特定基本数据类型的容器,所有缓冲区都具有以下属性:

  • capacity:容量,即缓冲区能够容纳的数据元素的最大数量,在创建时确定且不能更改。
  • position:位置,下一个要读取或写入的数据元素的索引。
  • limit:限制,缓冲区中可以读取或写入的最大位置,位于 limit 后的数据不可读写。
  • mark:标记,一个备忘位置,调用 mark () 方法将 mark 设为当前的 position 值,之后可通过 reset () 方法将 position 恢复到标记的位置。

常用方法

  • allocate(int capacity):创建一个指定容量的缓冲区
  • put(data):向缓冲区写入数据
  • flip():切换到读模式(将 limit 设为 position,position 设为 0)
  • get():从缓冲区读取数据
  • clear():清空缓冲区(将 position 设为 0,limit 设为 capacity)
  • rewind():重置缓冲区(将 position 设为 0,mark 被丢弃)

示例代码

import java.nio.IntBuffer;

public class BufferExample {
    public static void main(String[] args) {
        // 创建一个容量为10的IntBuffer
        IntBuffer buffer = IntBuffer.allocate(10);
        
        // 向缓冲区写入数据
        for (int i = 0; i < 5; i++) {
            buffer.put(i);
        }
        
        // 切换到读模式
        buffer.flip();
        
        // 从缓冲区读取数据
        while (buffer.hasRemaining()) {
            System.out.print(buffer.get() + " ");
        }
        System.out.println();
        
        // 重置缓冲区,以便再次读取
        buffer.rewind();
        
        // 再次读取数据
        System.out.println("Rewind后再次读取:");
        while (buffer.hasRemaining()) {
            System.out.print(buffer.get() + " ");
        }
        
        // 清空缓冲区
        buffer.clear();
        System.out.println("\nclear后position: " + buffer.position());
        System.out.println("clear后limit: " + buffer.limit());
    }
}
2. 通道(Channel)

通道是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。与流不同的是,通道是双向的,支持异步读写操作,且通道始终与缓冲区交互。

常用通道实现

  • FileChannel:用于文件读写
  • SocketChannel:用于 TCP 网络通信的客户端
  • ServerSocketChannel:用于 TCP 网络通信的服务器端
  • DatagramChannel:用于 UDP 网络通信

示例代码:使用 FileChannel 复制文件

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("source.txt");
             FileOutputStream fos = new FileOutputStream("destination.txt");
             FileChannel inChannel = fis.getChannel();
             FileChannel outChannel = fos.getChannel()) {
            
            // 创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            
            // 从输入通道读取数据到缓冲区
            while (inChannel.read(buffer) != -1) {
                // 切换到读模式
                buffer.flip();
                
                // 将缓冲区的数据写入输出通道
                outChannel.write(buffer);
                
                // 清空缓冲区,准备下一次读取
                buffer.clear();
            }
            
            System.out.println("文件复制完成");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3. 选择器(Selector)

选择器是 Java NIO 中实现非阻塞 IO 的核心组件,它允许一个线程处理多个通道的事件。通过使用选择器,线程可以监听多个通道的连接、数据到达等事件,从而高效地管理多个通道。

使用步骤

  1. 创建 Selector:Selector selector = Selector.open();
  2. 将通道注册到选择器:channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
  3. 轮询选择器:selector.select();
  4. 获取就绪的事件:Set<SelectionKey> selectedKeys = selector.selectedKeys();
  5. 处理事件:遍历 selectedKeys,根据 key 的类型处理相应的事件

示例代码:使用 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 NioServerExample {
    private static final int PORT = 8080;
    
    public static void main(String[] args) {
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            
            // 配置服务器套接字通道
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(false);
            
            // 将服务器套接字通道注册到选择器,监听接受连接事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            
            System.out.println("服务器启动,监听端口: " + PORT);
            
            // 轮询选择器
            while (true) {
                // 阻塞等待就绪的通道
                selector.select();
                
                // 获取就绪的事件集合
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
                
                // 处理每个就绪的事件
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    
                    // 处理接受连接事件
                    if (key.isAcceptable()) {
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        SocketChannel socketChannel = ssc.accept();
                        System.out.println("接受新连接: " + socketChannel);
                        
                        // 配置客户端通道为非阻塞模式,并注册到选择器,监听读取事件
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    }
                    
                    // 处理读取事件
                    else if (key.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        
                        // 读取客户端数据
                        int bytesRead = socketChannel.read(buffer);
                        if (bytesRead > 0) {
                            buffer.flip();
                            byte[] data = new byte[buffer.remaining()];
                            buffer.get(data);
                            String message = new String(data, "UTF-8");
                            System.out.println("收到客户端消息: " + message);
                            
                            // 回写响应给客户端
                            String response = "服务器已收到消息: " + message;
                            ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
                            socketChannel.write(responseBuffer);
                        } else if (bytesRead == -1) {
                            // 客户端关闭连接
                            System.out.println("客户端关闭连接: " + socketChannel);
                            socketChannel.close();
                        }
                    }
                    
                    // 移除已处理的事件
                    keyIterator.remove();
                }
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、非阻塞 IO 编程实践

1. 非阻塞客户端实现
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NioClientExample {
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 8080;
    
    public static void main(String[] args) {
        try (SocketChannel socketChannel = SocketChannel.open();
             Scanner scanner = new Scanner(System.in)) {
            
            // 连接服务器
            socketChannel.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT));
            System.out.println("已连接到服务器: " + socketChannel);
            
            // 配置为非阻塞模式
            socketChannel.configureBlocking(false);
            
            // 创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            
            // 读取用户输入并发送给服务器
            while (true) {
                System.out.print("请输入要发送的消息(输入exit退出): ");
                String message = scanner.nextLine();
                
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
                
                // 发送消息给服务器
                buffer.clear();
                buffer.put(message.getBytes("UTF-8"));
                buffer.flip();
                socketChannel.write(buffer);
                
                // 接收服务器响应
                buffer.clear();
                int bytesRead = socketChannel.read(buffer);
                if (bytesRead > 0) {
                    buffer.flip();
                    byte[] data = new byte[buffer.remaining()];
                    buffer.get(data);
                    String response = new String(data, "UTF-8");
                    System.out.println("收到服务器响应: " + response);
                }
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2. 基于选择器的多客户端处理

上面的 NioServerExample 已经展示了基于选择器的服务器实现,它可以同时处理多个客户端连接。关键点在于:

  • 服务器通道注册 OP_ACCEPT 事件以接受新连接
  • 客户端通道注册 OP_READ 事件以读取客户端数据
  • 单个线程通过 Selector 轮询所有注册的通道,处理就绪的事件

四、NIO 高级应用

1. 文件锁定

Java NIO 提供了文件锁定机制,可以锁定文件的一部分或全部,防止其他程序访问。

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public class FileLockingExample {
    public static void main(String[] args) {
        try (RandomAccessFile file = new RandomAccessFile("test.txt", "rw");
             FileChannel channel = file.getChannel()) {
            
            // 获取文件锁(锁定整个文件)
            FileLock lock = channel.lock();
            System.out.println("文件已锁定");
            
            // 执行文件操作
            // ...
            
            // 释放锁
            lock.release();
            System.out.println("文件锁已释放");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2. 内存映射文件

内存映射文件允许将文件的一部分或全部映射到内存中,这样可以像访问内存一样访问文件,提高 IO 效率。

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class MemoryMappedFileExample {
    private static final int SIZE = 1024 * 1024; // 1MB
    
    public static void main(String[] args) {
        try (RandomAccessFile file = new RandomAccessFile("mapped_file.dat", "rw");
             FileChannel channel = file.getChannel()) {
            
            // 创建内存映射文件
            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, SIZE);
            
            // 写入数据
            for (int i = 0; i < SIZE; i++) {
                buffer.put((byte) 'A');
            }
            
            // 读取数据
            buffer.position(0);
            for (int i = 0; i < 10; i++) {
                System.out.print((char) buffer.get());
            }
            System.out.println();
            
            System.out.println("内存映射文件操作完成");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3. 异步 IO(AIO)

Java 7 引入了 NIO.2,提供了真正的异步 IO 支持,通过 CompletionHandler 回调或 Future 对象获取操作结果。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.Future;

public class AioServerExample {
    private static final int PORT = 8090;
    
    public static void main(String[] args) throws IOException, InterruptedException {
        // 创建异步服务器套接字通道
        AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress(PORT));
        
        System.out.println("异步服务器启动,监听端口: " + PORT);
        
        // 接受客户端连接
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(AsynchronousSocketChannel clientChannel, Void attachment) {
                // 继续接受下一个连接
                serverChannel.accept(null, this);
                
                // 处理当前连接
                handleClient(clientChannel);
            }
            
            @Override
            public void failed(Throwable exc, Void attachment) {
                exc.printStackTrace();
            }
        });
        
        // 保持主线程运行
        Thread.sleep(Long.MAX_VALUE);
    }
    
    private static void handleClient(AsynchronousSocketChannel clientChannel) {
        try {
            System.out.println("接受新连接: " + clientChannel.getRemoteAddress());
            
            // 创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            
            // 异步读取数据
            clientChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer bytesRead, ByteBuffer buffer) {
                    if (bytesRead > 0) {
                        buffer.flip();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        String message = new String(data);
                        System.out.println("收到客户端消息: " + message);
                        
                        // 回写响应
                        String response = "服务器已收到: " + message;
                        ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
                        
                        // 异步写入响应
                        Future<Integer> writeResult = clientChannel.write(responseBuffer);
                        try {
                            writeResult.get(); // 等待写入完成
                            System.out.println("响应已发送给客户端");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } else if (bytesRead == -1) {
                        // 客户端关闭连接
                        try {
                            System.out.println("客户端关闭连接: " + clientChannel.getRemoteAddress());
                            clientChannel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                
                @Override
                public void failed(Throwable exc, ByteBuffer buffer) {
                    try {
                        System.out.println("读取失败,关闭连接: " + clientChannel.getRemoteAddress());
                        clientChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

五、NIO 性能优化建议

  1. 合理设置缓冲区大小:过小的缓冲区会增加 IO 操作次数,过大的缓冲区会浪费内存。

  2. 避免不必要的上下文切换:非阻塞 IO 的优势在于一个线程可以处理多个通道,避免创建过多线程导致上下文切换开销。

  3. 使用直接缓冲区:对于需要频繁进行 IO 操作的数据,使用直接缓冲区(ByteBuffer.allocateDirect())可以减少内存拷贝。

  4. 优化选择器配置:避免在选择器上注册过多通道,可根据系统资源和负载情况进行合理划分。

  5. 合理处理并发:在多线程环境下,注意对共享资源的同步访问,避免数据竞争。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2398006.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

T/CCSA 663-2025《医疗科研云平台技术要求》标准解读与深度分析

参考地址:https://www.doc88.com/p-30280431175529.html 引言 随着医疗信息化建设的深入推进,医疗行业正经历从"业务驱动"向"数据驱动"的转型。在这一背景下,中国通信标准化协会(CCSA)于2025年发布了T/CCSA 663-2025《医疗科研云平台技术要求》标准,并…

win11回收站中出现:查看回收站中是否有以下项: WPS云盘回收站

好久没更新了&#xff0c;首先祝所有大朋友、小朋友六一儿童节快乐&#xff0c;真的希望我们永远都不会长大呀&#xff0c;长大真的好累呀(•_•) 免责声明 笔者先来个免责声明吧&#xff0c;被网上的阴暗面吓到了 若读者参照笔者的这篇文章所执行的操作中途或后续出现的任何…

SCDN如何同时保障网站加速与DDoS防御?

在互联网时代&#xff0c;网站既要面对用户访问量的激增&#xff0c;又要抵御层出不穷的网络攻击&#xff0c;特别是DDoS攻击的威胁。SCDN&#xff08;安全内容分发网络&#xff09;作为融合加速与安全的解决方案&#xff0c;如何实现“加速”与“防御”的双重保障&#xff1f;…

项目前置知识——不定参以及设计模式

1.C语言不定参宏函数 c语言中&#xff0c;printf就是一个不定参函数&#xff0c;在使用不定参宏函数时&#xff0c;我们使用__VA_ARGS__来解析不定参&#xff1a; #include <iostream> #include <cstdarg>#define LOG(fmt/*格式*/, .../*用...表示不定参*/) prin…

04powerbi-度量值-筛选引擎CALCULATE()

1、calculate calculate 的参数分两部分&#xff0c;分别是计算器和筛选器 2、多条件calculater与表筛选 多条件有不列的多条件 相同列的多条件 3、calculatertable &#xff08;表&#xff0c;筛选条件&#xff09;表筛选 与calculate用法一样&#xff0c;可以用创建表&…

chromedriver 下载失败

问题描述 chromedriver 2.46.0 下载失败 淘宝https://registry.npmmirror.com/chromedriver/2.46/chromedriver_win32.zip无法下载 解决方法 找到可下载源 https://cdn.npmmirror.com/binaries/chromedriver/2.46/chromedriver_win32.zip &#xff0c;先将其下载到本地目录(D…

Weather app using Django - Python

我们的任务是使用 Django 创建一个 Weather 应用程序&#xff0c;让用户可以输入城市名称并查看当前天气详细信息&#xff0c;例如温度、湿度和压力。我们将通过设置一个 Django 项目&#xff0c;创建一个视图来从 OpenWeatherMap API 获取数据&#xff0c;并设计一个简单的模板…

机器视觉2,硬件选型

机器视觉1&#xff0c;学习了硬件的基本知识和选型&#xff0c;现在另外的教材巩固知识 选相机 工业相机选型的保姆级教程_哔哩哔哩_bilibili 1.先看精度多少mm&#xff0c;被检测物体长宽多少mm》分辨率&#xff0c; 选出合理范围内的相机 2.靶面尺寸&#xff0c;得出分…

电阻电容的选型

一、电阻选型 1.1安装方式 贴片电阻体积小&#xff0c;适用于SMT生产&#xff1b;功率小&#xff1b;易拆解插件电阻体积大&#xff1b;功率大&#xff1b;不易脱落 1.2阻值 电阻的阻值是离散的&#xff0c;其标称阻值根据精度分为E6、E12、E24、E48、E96、E192六大系列&am…

12.springCloud AlibabaSentinel实现熔断与限流

目录 一、Sentinel简介 1.官网 2.Sentinel 是什么 3.Sentinel 的历史 4.Sentinel 基本概念 资源 规则 5.Sentinel 功能和设计理念 (1).流量控制 什么是流量控制 流量控制设计理念 (2).断降级 什么是熔断降级 熔断降级设计理念 (3).系统自适应保护 6.主要工作机制…

vSOME/IP与ETAS DSOME/IP通信的问题解决方案

✅ 一、服务版本不匹配导致 Handover 问题 —— 需要更新 VSOMEIP 代码逻辑 📌 问题描述: 在 SOME/IP 通信中,发布者(offer)与订阅者(subscribe)之间存在服务版本不一致的问题,导致 Handover(切换)失败。 ✅ 解决方案: 需要在 offer_service 和 subscribe 接口中…

软考-系统架构设计师-第十五章 信息系统架构设计理论与实践

信息系统架构设计理论与实践 15.2 信息系统架构风格和分类15.3 信息系统常用的架构模型15.4 企业信息系统总体框架15.5 信息系统架构设计方法 15.2 信息系统架构风格和分类 信息系统架构风格 数据流体系结构风格&#xff1a;批处理、管道-过滤器调用/返回体系结构风格&#x…

MySQL 8 完整安装指南(Ubuntu 22.04)

MySQL 8 完整安装指南&#xff08;Ubuntu 22.04&#xff09; 本教程详细说明如何在 Ubuntu 22.04 上安装和配置 MySQL 8&#xff0c;包含安全优化及远程访问设置。 1️⃣ 添加 MySQL 官方 APT 仓库 官网仓库下载地址&#xff1a;MySQL APT 仓库下载页 下载仓库配置包&#…

安卓jetpack compose学习笔记-UI基础学习

哲学知识应该用哲学的方式学习&#xff0c;技术知识也应该用技术的方式学习。没必要用哲学的态度来学习技术。 学完安卓技术能做事就ok了&#xff0c;安卓技术肯定是有哲学的&#xff0c;但是在初学阶段没必要讨论什么安卓哲学。 学习一们复杂技术的路径有很多&#xff0c;这里…

蓝桥杯_DS18B20温度传感器---新手入门级别超级详细解析

目录 一、引言 DS18B20的原理图 单总线简介&#xff1a; ​编辑暂存器简介&#xff1a; DS18B20的温度转换与读取流程 二、代码配置 maic文件 疑问 关于不同格式化输出符号的使用 为什么要rd_temperature()/16.0&#xff1f; onewire.h文件 这个配置为什么要先读lo…

C++中锁与原子操作的区别及取舍策略

文章目录 锁与原子操作的基本概念锁&#xff08;Lock&#xff09;原子操作&#xff08;Atomic Operations&#xff09; 锁与原子操作的区别1. **功能**2. **性能**3. **复杂性**4. **适用场景** 锁与原子操作的取舍策略1. **简单变量操作**2. **复杂共享资源**3. **性能敏感场景…

C++语法系列之类型转换

前言 类型转换是经常存在的情况&#xff0c;类型转换分为隐式类型转化 和 显式类型转化 隐式类型转化&#xff1a;编译器在编译阶段自动进行&#xff0c;能转就转&#xff0c;不能转就编译失败 double i 3.3; int b i; //隐式类型转化 double -> intC搞出来了四种强制类…

详解开漏输出和推挽输出

开漏输出和推挽输出 以上是 GPIO 配置为输出时的内部示意图&#xff0c;我们要关注的其实就是这两个 MOS 管的开关状态&#xff0c;可以组合出四种状态&#xff1a; 两个 MOS 管都关闭时&#xff0c;输出处于一个浮空状态&#xff0c;此时他对其他点的电阻是无穷大的&#xff…

【八股消消乐】索引失效与优化方法总结

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本专栏《八股消消乐》旨在记录个人所背的八股文&#xff0c;包括Java/Go开发、Vue开发、系统架构、大模型开发、具身智能、机器学习、深度学习、力扣算法等相关知识点&#xff…

一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录——4. 配置服务器终端环境 zsh , oh my zsh, vim

前言 通过前面几篇文章&#xff0c;我们顺利的 安装了 ubuntu server 服务器&#xff0c;并且配置好了 ssh 免密登录服务器&#xff0c;也安装好了 服务器常用软件安装,接下来&#xff0c;我们要仔细的配置一下我们的终端环境&#xff0c;让服务器的终端更加好用。 一般情况下…