初识Java中的NIO

news2025/1/22 15:53:35

1.概述

  Java NIO 全称java non-blocking IO ,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出新特性,被统称为 NIO(即 New IO),是同步非阻塞的。NIO采用内存映射文件的方式来处理输入输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件。NIO与原来的IO有同样的作用,但是使用的方式完全不同, NIO支持面向缓冲区的、基于通道的IO操作。 NIO将以更加高效的方式进行文件的读写操作。

2.NIO 三大核心原理示意图

  NIO 有三大核心部分:Channel(通道)Buffer(缓冲区)Selector(选择器)。NIO是面向缓冲区编程。数据读取到一个buffer中(缓冲区),需要时可在缓冲区内前后移动,增加了处理过程中的灵活性,使用它可提供非阻塞式的伸缩性网络。对于非阻塞式的理解:通俗来说就是一个线程可以处理多个操作
在这里插入图片描述

  • 每个 channel 都会对应一个 Buffer;
  • Selector 对应一个线程, 一个线程对应多个 channel(连接);
  • 每个 channel 都注册到 Selector选择器上;
  • Selector不断轮询查看Channel上的事件, 根据不同的事件完成不同的操作;
  • Buffer 就是一个内存块 , 底层是一个数组,NIO的Buffer是可以读也是可以写的,channel是双向的。

2.1 缓冲区Buffer

  缓冲区实际上是一个容器对象,底层是一个数组,在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。具体看下面这张图就理解了:

在这里插入图片描述
上图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的数据写入通道。服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。

  在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于Java中的基本类型,基本都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:
在这里插入图片描述

注:可以看到除了Boolean 类型外,其它都有对应的Buffer。

Buffer四个成员变量的说明

private int mark = -1;
private int position = 0;
private int limit;
private int capacity;

一般来说,四个属性的关系应该属于:

​ 0 <= mark <= position <= limit <= capacity

属性说明
capacity容量,即可以容纳的最大数据量;在缓冲区创建时设置并且不能改变
limit上限,缓冲区中当前的数据量
position位置,缓冲区中下一个要被读或写的元素的索引
mark调用mark()方法来设置mark=position,再调用reset()可以让position恢复到mark标记的位置,即position=mark

在这里插入图片描述

由于缓存区是读写共存,所以不同的模式下,这两个变量的值也具有不同的意义。

写模式下,所谓写模式就是将缓存区中的内容写入通道(buffer–>channel)。position 代表下一个字节应该被写出去的字节在缓存区中的位置,limit 表示最后一个待写字节在缓存区的位置。

读模式下,所谓读模式就是从通道读取数据到缓存区(channel–>buffers)。position 代表下一个读出来的字节应当存储在缓存区的位置,limit 等于 capacity。

2.2 通道channel

  Channel和传统IO中的Stream很相似。虽然相似,但是有很大的区别,主要区别为:通道是双向的,通过一个Channel既可以读,也可以写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写,比如:InputStream只能进行读取操作,OutputStream只能进行写操作。但是通道和流一样都是需要基于物理文件的,而每个流或者通道都通过文件指针操作文件,这里说的「通道是双向的」也是有前提的,那就是通道基于随机访问文件『RandomAccessFile』的可读可写文件指针。

  通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

比喻:通常把IO比喻成为水流,管道就是水流的通道;把NIO比喻为火车的轨道,然后缓冲区就是上面的火车。
在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:
在这里插入图片描述

  Channel(通道)表示到实体如硬件设备、文件、网络套接字或可以执行一个或多个不同I/O操作的程序组件的开放的连接。所有的Channel都不是通过构造器创建的,而是通过传统的节点InputStream、OutputStream的getChannel方法来返回响应的Channel。

  Channel中最常用的三个类方法就是map、read和write,其中map方法用于将Channel对应的部分或全部数据映射成ByteBuffer,而read或write方法有一系列的重载形式,这些方法用于从Buffer中读取数据或向Buffer中写入数据。

2.3 选择器Selector

  Selector类是NIO的核心类,**Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。**这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。

  与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑。

  Selector选择器可以理解为一个IO事件的监听与查询器,通过选择器,一个线程可以查询多个通道的IO事件的就绪状态。

什么是IO事件?

表示通道某种IO操作已经就绪或者说已经做好了准备。

例如:如果一个新Channel连接建立成功,就会在Server Socket Channel上发生一个IO事件,代表一个新连接一个准备好,这个IO事件叫做“接收就绪”事件。

NIO定义了四个事件:SelectionKey.OP_ACCEPT、SelectionKey.OP_CONNECT、SelectionKey.OP_READ、SelectionKey.OP_WRITE

3.使用案例

3.1服务端

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.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

public class Server {
    public static void main(String[] args) throws Exception {
        //创建Selector对象,管理多个channel
        Selector selector = Selector.open();
        //创建ServerSocketChannel对象(绑定主机名和端口号)
        ServerSocketChannel ssc = ServerSocketChannel.open().bind(new InetSocketAddress("localhost", 8080));
        //设置服务为非阻塞(必须配置,否则报错)
        ssc.configureBlocking(false);
        // 通道可支持的操作:支持新的连接 监听ACCEPT事件
        // ServerSocketChannel仅支持接受新连接,因此此方法返回SelectionKey.OP_ACCEPT 。
        int ops = ssc.validOps();
        //将通道注册到selector 等待连接
        SelectionKey selectKy = ssc.register(selector, ops, null);
        for (; ; ) {// 无条件的循环
            // 检测当前选择器注册通道是否有就绪事件,如果没有就阻塞,有事件,线程才会恢复运行
            int noOfKeys = selector.select();
            if (noOfKeys <= 0) {
                continue;
            }
            // 获取就绪的事件,即为选择器键集合
            Set selectedKeys = selector.selectedKeys();
            Iterator itr = selectedKeys.iterator();
            while (itr.hasNext()) {
                SelectionKey ky = (SelectionKey) itr.next();
                //选择键事件为接收就绪事件
                if (ky.isAcceptable()) {
                    //获取客户端连接通道
                    SocketChannel client = ssc.accept();
                    //配置客户端为非阻塞
                    client.configureBlocking(false);
                    //重点关注:READ事件
                    client.register(selector, SelectionKey.OP_READ);
                    // 选择键为READ事件
                } else if (ky.isReadable()) {
                    //获取客户端SocketChannel通道
                    SocketChannel client = (SocketChannel) ky.channel();
                    //设置缓冲区大小
                    ByteBuffer buffer = ByteBuffer.allocate(256);
                    //客户端读取缓冲区数据
                    client.read(buffer);
                    String output = new String(buffer.array()).trim();
                    System.out.println("接收客户端信息: " + output);
                    ByteBuffer buffer1 = ByteBuffer.wrap(("服务端时间戳:"+System.currentTimeMillis()).getBytes(StandardCharsets.UTF_8));
                    //将数据写回缓冲区
                    client.write(buffer1);
                }
                itr.remove();// 将选择键清空,防止下次循环时被重复处理
            }
        }
    }
}

3.2 客户端

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

public class Client {
    public static void main(String[] args) throws Exception {
        // 创建InetSocketAddress对象,绑定主机名和端口号
        InetSocketAddress hA = new InetSocketAddress("localhost", 8080);
        //获取客户端SocketChannel通道
        SocketChannel client = SocketChannel.open(hA);
        System.out.println("The Client is sending messages to server...");
        for (; ; ) {
            //实例化缓冲区对象
            ByteBuffer buffer = ByteBuffer.wrap(("客户端时间戳:" + System.currentTimeMillis()).getBytes(StandardCharsets.UTF_8));
            //数据写入缓冲区
            client.write(buffer);
            //清空缓冲区
            buffer.clear();
            //设置新缓冲区的大小
            ByteBuffer buffer1 = ByteBuffer.allocate(256);
            //读取新缓冲区数据
            client.read(buffer1);
            //打印结果到控制台
            System.out.println("接收服务器消息:" + new String(buffer1.array(), StandardCharsets.UTF_8).trim());
            TimeUnit.SECONDS.sleep(3);
        }

    }
}

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

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

相关文章

【C++进阶】哈希表(哈希函数、哈希冲突、开散列、闭散列)

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 总有光环在陨落&#xff0c;总有新星在闪烁 引言&#xff1a; 我们之前…

CSS 学习笔记 总结

CSS 布局方式 • 表格布局 • 元素定位 • 浮动布局&#xff08;注意浮动的负效应&#xff09; • flex布局 • grid布局&#xff08;感兴趣的可以看下菜鸟教程&#xff09; 居中设置 元素水平居中 • 设置宽度后&#xff0c;margin设置为auto • 父容器设置text-alig…

鸿蒙原生应用已超4000个!

鸿蒙原生应用已超4000个&#xff01; 来自 HarmonyOS 微博近期消息&#xff0c;#鸿蒙千帆起# 重大里程碑&#xff01;目前已有超4000个应用加入鸿蒙生态。从今年1月18日华为宣布首批200多家应用厂商正在加速开发鸿蒙原生应用&#xff0c;到3月底超4000个应用&#xff0c;短短…

【ARMv7-M】| 01——阅读笔记 | 简介|应用程序级编程和内存模型

系列文章目录 【ARMv7-M】| 01——阅读笔记 | 简介|应用程序级编程和内存模型 失败了也挺可爱&#xff0c;成功了就超帅。 文章目录 前言1、简介2、应用程序级编程模型2.1 编程模式和访问等级2.2 数据类型和运算操作2.3 寄存器和执行状态1.2.4 异常和中断1.2.5 浮点单元寄存器…

android 资源文件混淆

AGP7.0以上引用AndResGuard有坑 记录下 在项目的build.gradle中添加如下 buildscript {ext.kotlin_version "1.4.31"repositories {google()jcenter()maven {url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}}dependencies {class…

【RISC-V 指令集】RISC-V 向量V扩展指令集介绍(七)- 向量算术指令格式

1. 引言 以下是《riscv-v-spec-1.0.pdf》文档的关键内容&#xff1a; 这是一份关于向量扩展的详细技术文档&#xff0c;内容覆盖了向量指令集的多个关键方面&#xff0c;如向量寄存器状态映射、向量指令格式、向量加载和存储操作、向量内存对齐约束、向量内存一致性模型、向量…

成功案例(IF=13.263)| 基因组+重测序+GWAS揭示豇豆传播过程中基因组的变化及荚果相关性状的遗传调控关键位点

研究背景 豇豆&#xff08;Vigna unguiculata (L.) Walp.&#xff09;&#xff0c;豆科的一员&#xff0c;是一种一年生的热带或亚热带草本植物&#xff0c;用于人类食物和动物饲料&#xff0c;作为青粪、干草或青贮饲料。有两种主要栽培亚种Vigna unguiculata ssp. sesquiped…

文档管理系统解决方案(word原件)

1.系统概述 1.1.需求描述 1.2.需求分析 1.3.重难点分析 1.4.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 数据备份技术 3.系统功能设计 3.1.功能清单列表 3.2.基础数据管理 3.3.位置管理 3.4.文档使用 3.5.文档管理 软件全套资料包获取方式①&#xff1a;软件项…

leetcode热题100.接雨水

Problem: 42. 接雨水 文章目录 题目思路复杂度Code 题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 …

pyside6,“提升为”的部件使用困惑

在Qt designer中&#xff0c;新建一个QMainWindow&#xff0c;新建一个QWidget&#xff0c;并命名为widget&#xff0c;如图&#xff1a; 新建NewClass.py&#xff0c;输入代码&#xff1a; # encoding: utf-8 from PySide6.QtWidgets import QWidgetclass NewClass(QWidget):…

【JavaScript】原型链/作用域/this指针/闭包

1.原型链 参考资料&#xff1a;Annotated ES5 ECMAScript起初并不支持如C、Smalltalk 或 Java 中“类”的形式创建对象&#xff0c;而是通过字面量表示法或者构造函数创建对象。每个构造函数都是一个具有名为“prototype”的属性的函数&#xff0c;该属性用于实现基于原型的继…

mysql的索引类型与数据存储

mysql索引与类型 什么是索引&#xff1f; 索引&#xff08;Index&#xff09;是帮助MySQL高效获取数据的数据结构。我们可以简单理解为&#xff1a;快速查找排好序的一种数据结构。Mysql索引主要有两种结构&#xff1a;BTree索引和Hash索引。我们平常所说的索引&#xff0c;如…

真实的招生办对话邮件及美国高校官网更新的反 AI 政策

这两年 ChatGPT 的热度水涨船高&#xff0c;其编写功能强大&#xff0c;且具备强大的信息整合效果&#xff0c;所以呈现的内容在一定程度上具备可读性。 那么&#xff0c;美国留学文书可以用 ChatGPT 写吗&#xff1f;使用是否有风险&#xff1f;外网博主 Kushi Uppu 在这个申…

鸿蒙学习记录

问题小测记录 总结链接&#xff1a;小测总结 学习笔记&#xff1a;鸿蒙开发学习记录 1、 main_pages.json存放页面page路径配置信息。 2、在stage模型中&#xff0c;下列配置文件属于AppScope文件夹的是&#xff1f; app.json5 3、module.json5配置文件中&#xff0c;包含…

自然语言处理技术(Natural Language Processing)知识点

自然语言处理知识点 自然语言处理1. word2vec是什么2. 常用的NLP工具和软件3. 朴素贝叶斯分类器4. BiLSTM-CRF模型怎么去实现5. Bert模型实现NER6. 命名实体识别任务中&#xff0c;怎么去处理数据分布不均的问题&#xff1f;7. 用户问题检索相关文本时&#xff0c;具体都用了哪…

解决报错 npm ERR! Missing script:“serve“

项目场景&#xff1a; 在运行vue项目时&#xff0c;遇到报错&#xff01;导致无法启动&#xff01; 问题描述 报错&#xff1a;npm犯错!缺少脚本:“serve” npm ERR! Missing script:"servenpmERR!ERR! To see a list of scripts, run:noMnpmERR!npm runnpm ERR! A compl…

HiveSQL如何生成连续日期剖析

HiveSQL如何生成连续日期剖析 情景假设&#xff1a; 有一结果表&#xff0c;表中有start_dt和end_dt两个字段&#xff0c;&#xff0c;想要根据开始和结束时间生成连续日期的多条数据&#xff0c;应该怎么做&#xff1f;直接上结果sql。&#xff08;为了便于演示和测试这里通过…

一个项目的SpringCloud微服务改造过程

SSO是公司一个已经存在了若干年的项目&#xff0c;后端采用SpringMVC、MyBatis&#xff0c;数据库使用MySQL&#xff0c;前端展示使用Freemark。今年&#xff0c;我们对该项目进行了一次革命性的改进&#xff0c;改造成SpringCloud架构&#xff0c;并且把前后端分离&#xff0c…

QT中使得界面类相关输出打印到控制台上

如下图所示&#xff0c;全部输出到了控制台上&#xff1a; 如何设置&#xff1f; 1、设置 Run in teminal 2、pro文件中设置 CONFIG console选项 3、clean -> qmake -> run&#xff0c;此时观察到已经输出到控制台上了

Day16_学点儿JavaEE_理论知识_Tomcat、JSP、Servlet

1 软件的结构 C/S (Client - Server 客户端-服务器端) 典型应用&#xff1a;QQ软件 &#xff0c;飞秋&#xff0c;印象笔记。 特点&#xff1a; 必须下载特定的客户端程序。服务器端升级&#xff0c;客户端升级。 B/S &#xff08;Broswer -Server 浏览器端- 服务器端&…