首页 课程 师资 教程 报名

Nio原理详解

  • 2022-05-17 09:51:42
  • 1668次 星辉

1. 阻塞和同步

(1)阻塞(Block)和非租用(NonBlock):

阻塞和非阻塞是在进程访问数据时处理数据是否就绪的一种方式。数据未就绪时阻塞:往往需要等待缓冲区中的数据就绪后再处理其他事情,否则会一直阻塞队列。在那里等着。

非阻塞:当我们的进程访问我们的数据缓冲区时,如果数据没有准备好,则直接返回,无需等待。如果数据准备好了,也直接返回

(2)同步和异步方法:

同步和异步都基于应用程序的私有操作系统处理 IO 事件的方式。比如同步:应用程序直接参与IO读写操作。异步:所有IO读写都交给操作系统处理,应用只需要等待通知即可。

在同步模式下处理IO事件时,必须阻塞在某个方法上,等待我们的IO事件完成(阻塞IO事件或者通过轮询IO事件)。对于异步,所有的IO读写都交给操作系统来推。这时候,我们可以做其他事情,而不是完成真正的IO操作。当操作完成 IO 时,它会给我们的应用程序一个通知

同步:阻塞直到 IO 事件,阻塞直到读变为写。这时候我们根本不能自己动手,让读写方法加到线程中,然后阻塞线程来实现,线程的性能开销比较大。

2.BIO与NIO的对比

块 IO 和非块 IO

(1)区别

IO模型 I NIO
方式 从硬盘到内存 从内存到硬盘
沟通 面对溪流(乡间小路) 面向缓存(高速公路、多路复用技术)
处理 阻塞 IO(多线程) 非阻塞 IO(反应堆)
扳机 没有 选择器(轮询机制)

(2)Stream Oriented vs Buffer Oriented

Java NIO 和 IO 的第一个大区别是 IO 是面向流的。NIO 是面向缓冲区的。Java IO是面向流的,意味着每次从流中读取一个字节,直到所有字节都被读取完毕,它们不会被缓存到任何地方,而且它不能在流中来回移动数据。如果需要来回移动从流中读取的数据,则需要先将其缓冲在缓冲区中。Java NIO 的面向缓冲区的方法略有不同。数据被读入稍后处理的缓冲区,根据需要在缓冲区中来回移动。这增加了处理的灵活性。但是,您还需要检查缓冲区是否包含您需要处理的所有数据。另外,确保当更多数据被读入缓冲区时,它不会覆盖缓冲区中未处理的数据。

(3)阻塞和非阻塞Java IO的各种流都是阻塞的。

这意味着当线程调用 read() 或 write() 时,线程会被阻塞,直到读取了一些数据,或者数据被完全写入。在此期间线程不能再做任何事情。Java NIO 的非阻塞模式使线程可以发送请求从通道读取数据,但它只能获取当前可用的数据。如果当前没有可用的数据,它不会得到任何东西。线程可以继续做其他事情,直到数据可供读取,而不是保持线程阻塞。非阻塞写入也是如此。线程请求将一些数据写入通道,但不需要等待它完全写入,线程可以在此期间做其他事情。线程通常会花费非阻塞 IO 空闲时间在其他通道上执行 IO 操作,因此单个线程现在可以管理多个输入和输出通道(通道)。

(4)选择器(Selector)

Java NIO的选择器允许单线程监控多个输入通道,可以注册多个通道使用一个选择器,然后使用单独的线程“选择”通道:这些通过已经有传入可以被处理,或者选择一个准备好写入的通道。这种选择机制使单个线程可以轻松管理多个通道。

(5)NIO和BIO读取文件

BIO读取文件

BIO从阻塞流中逐行读取数据

NIO读取文件:

通道是数据的载体,缓冲区是数据存放的地方,线程从缓冲区中检查数据并每次通知通道

(6)处理数据的线程数

NIO:一个线程管理多个连接

BIO:一个线程管理一个连接

3.蔚来简介

在 Java 1.4 之前的 I/O 系统中,提供的都是面向流的 I/O 系统。系统一次处理一个字节的数据,一个输入流产生一个字节的数据,一个输出流消耗一个字节 面向流的I/O很慢,Java 1.4引入了NIO,是面向块的输入输出系统。系统以块为单位进行处理,每个操作都是一步生成或消耗的。对于数据库,以块为单位处理数据比以字节为单位处理数据要快得多。

NIO中有几个核心对象需要掌握:Buffer、Channel、Selector。

4. 缓冲

缓冲区实际上是一个容器对象,或者更直接地说,它实际上是一个数组。在 NIO 库中,所有数据都使用缓冲区进行处理。读取数据时,直接读入缓冲区;写入数据时,也写入缓冲区;每次访问 NIO 中的数据时,都会将其放入缓冲区。在面向流的 I/O 系统中,所有数据都直接写入或直接读取到 Stream 对象中。

在 NIO 中,所有的缓冲区类型都继承自抽象类 Buffer。最常用的是ByteBuffer。对于Java中的基本类型,基本上都有特定的Buffer类型与之对应。它们之间的继承关系如下图所示。

(1)四个属性的

含义如下:

容量:缓冲区可以容纳的最大数据元素数。此容量是在创建缓冲区时设置的,并且永远无法更改。

上限(Limit):缓冲区的第一个不能被读写的元素。换句话说,缓冲区中现有元素的计数。

位置:要读取或写入的下一个元素的索引。该位置由相应的 get( ) 和 put( ) 函数自动更新。

标记:要读取或写入的下一个元素的索引。该位置由相应的 get( ) 和 put( ) 函数自动更新。

(2)Buffer的常用方法如下:

flip():将写模式转换为读模式

rewind():将位置重置为0,一般用于重复读。

clear() :

compact(): 将未读数据复制到缓冲区的头部。

mark(): reset():mark 可以标记一个位置,reset 可以重置到那个位置。

缓冲区常见类型: ByteBuffer 、 MappedByteBuffer 、 CharBuffer 、 DoubleBuffer 、 FloatBuffer 、 IntBuffer 、 LongBuffer 、 ShortBuffer 。

(3)基本操作

缓冲区基本操作:链接

缓冲区分片、缓冲区分配、直接缓冲区、缓冲区映射、缓冲区只读:链接

(4) 缓冲区访问数据流

存储数据时,位置为++,停止读取数据时,

调用flip()。此时limit=position,

读取数据时position=0,position++,直到limit

clear()清空缓冲区,准备再次写入(position变为0,limit变为容量)。

5.频道

通道是一个可以读写数据的对象,当然所有的数据都是通过一个Buffer对象来处理的。我们从不直接将字节写入通道,而是将数据写入包含一个或多个字节的缓冲区。此外,不是直接从通道读取字节,而是从通道将数据读取到缓冲区中,然后从缓冲区中检索字节。

在 NIO 中,提供了多种通道对象,所有通道对象都实现了 Channel 接口。它们之间的继承关系如下图所示:

(1)使用NIO读取

数据前面我们说过,每当读取数据时,并不是直接从channel读取,而是从channel读取到buffer。所以使用 NIO 读取数据可以分为以下三个步骤:

1)从 FileInputStream 中获取 Channel

2)创建 Buffer

3)从 Channel 读取数据到 Buffer

示例:链接

(2)使用NIO写入数据

使用NIO写入数据的过程与读取数据的过程类似。同样,数据不是直接写入通道,而是写入缓冲区,可以分为以下三个步骤:

1)从 FileInputStream中获取通道

2)创建缓冲区

3)将数据从通道写入缓冲区

示例:链接

6.反应堆

(1)阻塞IO模型

在旧的IO包中,serverSocket和socket都是阻塞的,所以一旦出现大规模并发行为,每次访问都会开启一个新线程。这时候会有大规模的线程上下文切换操作(因为都在等待,所以资源都被现有线程吃掉了),这个时候不管是等待线程还是处理线程,响应率会下降,并且影响新线程。

(2) NIO

Java NIO 在 jdk1.4 中使用,可以称为“新 IO”或非阻塞 I/O。下面是java NIO的工作原理:

1)一个专门的线程处理所有的IO事件,负责分发。

2)事件驱动机制:事件到达时触发,而不是同步监听事件。

3)线程通信:线程之间通过wait、notify等方式进行通信,确保每一次上下文切换都是有意义的。减少不必要的线程切换。

注意:每个线程的处理流程大概是读取数据、解码、计算、编码、发送响应。

7. 选择器

传统的服务器/客户端模型将基于 TPR(每个请求的线程)。服务器将为每个客户端请求创建一个线程。该线程单独处理客户端请求。这个模型的一个问题是线程的数量急剧增加。大量线程会增加服务器的开销。为了避免这个问题,大多数实现都采用线程池模型,并设置线程池中的最大线程数,这带来了新的问题。如果线程池1线程中有200个线程,有200个用户在下载大文件,那么第201个用户的请求无法及时处理,即使第201个用户只想请求几个KB大小的页面。传统的 Sorvor/Client 模式如下:

NIO 中的非阻塞 IO 采用基于 Reactor 模式的工作方式,IO 调用不会被阻塞,而是注册具有有趣特性的 IO 事件,例如可读数据的到来, new Sockets等,系统会在发生hold rate事件时通知我们。Selector,NlO中非阻塞IO的核心设计,是注册各种IO事件的地方,当这些事件发生时,这个对象告诉我们发生了什么。

当任何注册的事件发生时,如读或写,都可以从Selector中获取对应的SelectionKey,从SelectionKey中可以找到该事件和该事件发生的具体SelectableChannel,从而获取客户端发送的数据。

在 NIO 中使用非阻塞 IO 编写服务器处理程序需要三个步骤。

(1)将感兴趣的事件注册到Selector对象中

(2)从Selector中获取感兴趣的事件

(3)根据不同的事件进行相应的处理

通过上述介绍,相信大家对Nio原理已经有所了解,大家如果对此比较感兴趣,想了解更多相关知识,不妨来关注一下星辉的NIO视频教程,视频教程由浅到深,通俗易懂,很适合没有基础的小伙伴学习,希望对大家能够有所帮助哦。

选你想看

你适合学Java吗?4大专业测评方法

代码逻辑 吸收能力 技术学习能力 综合素质

先测评确定适合在学习

在线申请免费测试名额
价值1998元实验班免费学
姓名
手机
提交