Java-NIO 开篇(1)

NIO简介

高性能的Java通信,离不开Java NIO组件,现在主流的技术框架或中间件服务器,都使用了Java NIO组件,譬如Tomcat、 Jetty、 Netty、Redis、RabbitMQ等的网络通信模块。在1.4版本之前, Java IO类库是阻塞式IO;从1.4版本开始,引进了新的异步IO库,被称为Java New IO类库,简称为Java NIO。 称“老的”阻塞式Java IO为OIO(Old IO)。总体上说, NIO弥补了原来面向流的OIO同步阻塞的不足,它为标准Java代码提供了高速的、面向缓冲区的IO。 学习NIO最主要的要理解Channel(通道)、Selector(选择器)、Buffer(缓冲区)三个核心组件。

在Java中, NIO和OIO的区别,主要体现在三个方面:

  • OIO是面向流(Stream Oriented)的, NIO是面向缓冲区(Buffer Oriented)的。

    如何理解呢?非常简单,假设两个计算机的进程建立了通信,OIO的 read() 操作总是以输入流式的方式顺序地读取读取一个或多个字节,期间要么读要么不读,不能随意乱序读取。而写数据也只能往输出流写入数据。输入流和输出流是单向数据传输的。而NIO引入了Channel(通道)和Buffer(缓冲区)的概念。面向缓冲区的读取和写入,都是与Buffer进行交互。用户程序只需要从通道中读取数据到缓冲区中,或将数据从缓冲区中写入到通道中。 NIO不像OIO那样是顺序操作,可以随意地读取Buffer中任意位置的数据,可以随意修改Buffer中任意位置的数据。如下图所示,通道处于应用层,所有的通信都需要经过Channel通道:

  • OIO的操作是阻塞的,而NIO的操作是非阻塞的。

    OIO阻塞: 当线程执行到read()和write()和连接服务器时的方法期间,如果没有对应的IO事件发生(客户端发送了连接请求、客户端发数据过来了)则一直在这些代码处等待,期间不做任何事情,线程挂起阻塞。直到发生了相应的IO事件才继续往下执行。并且如果还有其他的客户端触发了不是导致阻塞的IO事件也可能被阻塞。假设有两个客户端A和B,服务器伪代码如下所示,当没有客户端建立连接时则下面代码阻塞在第2行,假设此时A请求连接,那么下面代码就执行第2行然后阻塞在第3行。此时B也请求建立连接,这时候B是无法连接的,因为服务器代码阻塞在第3行。如果此时A发送了一个消息,那么下面程序继续执行,获取到A发生过来的消息msg。程序继续执行到第2行,如果因为B还在请求,直到这个时候B才能连接上服务器程序。这个例子就是OIO阻塞。

    // OIO阻塞伪代码,先建立连接在读取客户端发来的数据的代码
    1  while(true){
    2  	 sc = ssc.accept() // 接受客户端连接,阻塞方法
    3    msg = sc.read() // 读取客户端发送过来的数据,阻塞方法
    4  }
    

    NIO非阻塞: NIO的非阻塞实现方法是IO多路复用技术,通过selector来监测事件,如果没有事件发生则进行阻塞,如果有IO事件发生则获取到对应的事件处理对应的事件即可。首先A、B客户端没有连接时,下面程序执行到第2行进行阻塞,因为没有IO事件发生,如果此时A发起了连接则程序执行到第5行代码建立连接,如果此时B突然也请求连接,下面代码因为没有被阻塞经过10纳秒左右就能运行到第2行代码,然后发现B客户的请求连接事件,于是继续往下执行,同样执行到第5行代码。如果B请求连接的同时A突然发生了消息呢?那么没关系,events中有两个事件,通过for循环都能处理,A的消息发生请求会在第7行代码处理掉。下面的代码只是NIO的伪代码,实际上用法有一点点语法上的区别,逻辑是这样的。可以看见NIO是不会阻塞其他客户端的事件的,一般阻塞指的是较长时间的等待,例如1秒或者以上,下面也是单线程的服务器代码。如果你非要说10纳秒也是阻塞,那么下面的代码也确实无法0秒响应,实际上世界上也不存在这样的代码。什么是阻塞,我相信你已经有了自己的认知了!

    // NIO非阻塞伪代码,先建立连接在读取客户端发来的数据的代码
    1 while(true){
    2    events = selector.select() // 没有事件线程阻塞,有事件则接着往下运行
    3    for(E e : event){
    4        if(e是请求事件){
    5            e.connect();
    6        }else if (e是读事件){
    7            msg = e.read();
    8        }....
    9    }
    10 }
    
  • OIO没有选择器( Selector)概念,而NIO有选择器的概念。

    NIO技术的实现, 是基于底层的IO多路复用技术实现的,比如在Windows中需要select多路复用组件的支持,在Linux系统中需要select/poll/epoll多路复用组件的支持。 所以NIO的需要底层操作系统提供支持。而OIO不需要用到选择器selector,相信上面的NIO伪代码让你见识到了selector的威力!

到这里,你已经基本直到了NIO有多么牛了吧,但是还不够,前辈们的核心思想学习还刚刚开始,接下来需要学习非常核心的NIO类库的三大组件:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。

Channel(通道)

Channel的角色和OIO中的Stream(流)是差不多的。 在OIO中,同一个网络连接会关联到两个流:一个输入流( Input Stream),另一个输出流(Output Stream), Java应用程序通过这两个流,不断地进行输入和输出的操作。在NIO中,一个网络连接使用一个Channel( 通道) 表示,所有的NIO的IO操作都是通过连接通道完成的。一个通道类似于OIO中的两个流的结合体,既可以从通道读取数据,也可以向通道写入数据。Channel和Stream的一个显著的不同是: Stream是单向的,譬如InputStream是单向的只读流, OutputStream是单向的只写流; 而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。如下图所示:

在这里插入图片描述

NIO中的Channel的主要实现有:

  1. FileChannel 用于文件IO操作
  2. DatagramChannel 用于UDP的IO操作
  3. SocketChannel 用于TCP的传输操作
  4. ServerSocketChannel 用于TCP连接监听操作

Selector(选择器)

首先回顾一下IO多路复用,指的是一个进程/线程可以同时监视多个socket连接,一旦其中的一个或者多个连接可读或者可写,该监听进程/线程能够进行IO事件的查询。在Java应用层面,Selector 选择器可以理解为一个IO事件的监听与查询器。通过选择器,一个线程可以查询多个通道的IO事件的就绪状态。什么是IO事件呢?表示通道某种IO操作已经就绪、或者说已经做好了准备。例如,如果一个新Channel连接建立成功了,就会在Server Socket Channel上发生一个IO事件,代表一个新连接一个准备好,这个IO事件叫做“接收就绪”事件。 再例如, 一个Channel通道如果有数据可读,就会发生一个IO事件,代表该连接数据已经准备好,这个IO事件叫做 ―“读就绪”事件。Java NIO将NIO事件进行了简化,只定义了四个事件,这四种事件用SelectionKey的四个常量来表示:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

Selector本质就是去查询这些IO就绪事件的,从编程实现维度来说, IO多路复用编程的第一步,是把通道Channel注册到选择器中,第二步则是通过选择器Selector所提供的事件查询(select)方法,这些注册的通道是否有已经就绪的IO事件(例如可读、可写、网络连接完成等)。由于一个选择器只需要一个线程进行监控,所以,我们可以很简单地使用一个线程,通过选择器去管理多个连接通道。与OIO相比, NIO使用选择器的最大优势:系统开销小,系统不必为每一个网络连接(文件描述符)创建进程/线程,从而大大减小了系统的开销。总之, 通过Java NIO可以达到一个线程负责多个连接通道的IO处理, 这是非常高效的。这种高效,恰恰就来自于Java的选择器组件Selector以及其底层的操作系统IO多路复用技术的支持。

缓冲区(Buffer)

应用程序通过缓冲区与通道建立数据读写交互,Buffer顾名思义:缓冲区,实际上是一个容器,一个连续数组。 Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须经过Buffer。 如下图所示:

在这里插入图片描述

所谓通道的读取,就是将数据从通道读取到缓冲区中;所谓通道的写入,就是将数据从缓冲区中写入到通道中。缓冲区的使用,是面向流进行读写操作的OIO所没有的,也是NIO非阻塞的重要前提。Buffer类是一个抽象类,对应于Java的主要数据类型,在NIO中有8种缓冲区类,分别如下: ByteBuffer、 CharBuffer、 DoubleBuffer、 FloatBuffer、 IntBuffer、 LongBuffer、 ShortBuffer、MappedByteBuffer。 不同的Buffer子类,其能操作的数据类型能够通过名称进行判断,比如IntBuffer只能操作Integer类型的对象。 最常用的是ByteBuffer。如ByteBuffer子类就拥有一个byte[]类型的数组成员final byte[] hb,作为自己的读写缓冲区,数组的元素类型与Buffer子类的操作类型相互对应。那么在接下来的博文中将介绍Buffer具体的用法。

总结

NIO的知识体系就是围绕Buffer(缓冲区)、 Channel(通道)、Selector(选择器)三大核心组件的,下面学习只需要学习这三个核心组件的用法即可,最后将结合代码举例说明!

经典神书推荐:《Java高并发核心编程系列》——尼恩

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HCIP之BGP小实验

华子目录 实验拓扑及要求规划IP地址和网段实验步骤配置IP地址先让AS内部通建立BGP邻居查看邻居关系修改ospf中环回的接口网络类型宣告路由R1上有两个环回分别为192.168.1.0/24和192.168.2.0/24,只允许学到汇总和1.0R7上有两个环回172.16.1.0/24和172.16.2.0/24&…

浅谈双碳背景下能耗在线监测系统在节能管理中的实现

叶根胜 安科瑞电气股份有限公司 上海嘉定 201801 摘要:开展能耗在线监测系统建设,对加强政府部门和企业节能管理中的应用前景,分析系统在能源消费预测分析、能效对标、节能监察、能源精细化管理、用能权交易、宣传推广等方面的应用成效&…

Kafka生产消费流程

Kafka生产消费流程 1.Kafka一条消息发送和消费的流程图(非集群) 2.三种发送方式 准备工作 创建maven工程&#xff0c;引入依赖 <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>3.3.1…

高通sm7250与765G芯片是什么关系?(一百八十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

[自动化分布式] Zabbix自动发现与自动注册

abbix 自动发现&#xff08;对于 agent2 是被动模式&#xff09; zabbix server 主动的去发现所有的客户端&#xff0c;然后将客户端的信息登记在服务端上。 缺点是如果定义的网段中的主机数量多&#xff0c;zabbix server 登记耗时较久&#xff0c;且压力会较大 部署 添加zabb…

TCP的三次握手,四次挥手

三次握手 第一次握手&#xff1a;客户端发送SYN报文&#xff0c;井发送seq为x序列号给服务端&#xff0c;等待服务端的确认第二次握手&#xff1a;服务端发送SYNACK报文&#xff0c;并发送seq为Y的序列号&#xff0c;在确认序列号为x1第三次握手&#xff1a;客户端发送ACK报文&…

【MySQL】函数

函数 函数是指一段可以直接被另一段程序调用的程序或代码。 字符串函数 select 函数(参数);select concat(hello, mysql);select LPAD(01,5,-);select trim( hello mysql );select SUBSTRING(hello world,1,5);由于业务需求变更&#xff0c;企业员工的工号&#xff0c;统一为5位…

RaspberryPi(树莓派)图形界面的默认背景

一直都想知道 RaspberryPi&#xff08;树莓派&#xff09;安装后的默认图片背景是哪里&#xff0c;看起来还真的很漂亮有意境。 考古 后来上网考古了下发现了下面的信息&#xff1a; LI RIVER, YANGSHUO, CHINA Gear: Canon 5D MkII, EF 17-40mm f/4L Exposure: 1/40s, f/4…

USB转SPI USB转IIC 串口转SPI串口转IIC SPI I2C模块

一款支持USB转SPI、USB转I2C、USB转GPIO、USB转PWM、USB转ADC的模块。提供上位机工具&#xff0c;开发协议。 资料下载&#xff0c;链接&#xff1a;https://pan.baidu.com/s/1sw3RCMwjhrMO4qzUBq9bjA 提取码&#xff1a;qzjp 概述 串口转多协议模组为了客户调试一些功能…

gin+gorm增删改查目录框架

从网上找资料,发现,很多都是直接的结构 路由&#xff0c;后端的controller层&#xff0c;还有model层&#xff0c;都是放在了同一个main.go文件中&#xff0c;如果写项目的话&#xff0c;还得自己去拆文件&#xff0c;拆代码&#xff0c;经过查询和自己总结&#xff0c;下面放…

Numpy的学习 第一课 了解以及使用

1.输入模式 1.编辑模式 绿色2.命令模式 蓝色 2.运行 直接输入jupyter notebook 3.文档注释 查看函数帮助文档命令 help(函数) 单问号与多问号 单问号显示文档 多问号显示文档代码 3.shifttab 显示参数 4.运行外部文件 %run 路径,可绝对可相对 这里运行了就相当于方法了,或者…

【已解决】fatal: Authentication failed for ‘https://github.com/.../‘

文章目录 异常原因解决方法 异常原因 在 Linux 服务器上使用git push命令&#xff0c;输入用户名和密码之后&#xff0c;总会显示一个报错&#xff1a; fatal: Authentication failed for https://github.com/TianJiaQi-Code/Linux.git/ # 致命&#xff1a;无法通过验证访问起…