IO概述

IO

Input Output

字节流

输入流 输出流

字符流

输入流 输出流

节点流: FileInputStream 直接包含数据源 read0();

处理流: BufferInputStream 包含一个流对象 可以对流操作进行封装

​ ObjectInputStream

​ new Scanner(System.in).next(); 阻塞式IO 停止

计算机角度的IO

我们常说的输入输出,就是计算机的输入输出,计算机就是主体。

计算机分成分为 5 个部分:运算器、控制器、存储器、输入设备、输出设备。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iFwkrNmk-1691206180576)(C:\Users\ycj\AppData\Roaming\Typora\typora-user-images\1691204205558.png)]
输入设备是向计算机输入数据和信息的设备,键盘,鼠标都属于输入设备;输出设备是计算机硬件系统的终端设备,用于接收计算机数据的输出显示,一般显示器、打印机属于输出设备。

操作系统角度的IO

操作系统负责计算机的资源管理和进程的调度,我们电脑上跑着的应用程序,其 实是需要经过操作系统,才能做一些特殊操作,如磁盘文件读写、内存的读写等等。

真正的IO是在操作系统执行的。即应用程序的 IO 操作分为两种动作:IO 调用和IO 执行 。IO 调用是由进程(应用程序的运行态)发起,而 IO 执行是操作系统内核的工作。

应用程序发起的一次 IO 操作包含两个阶段:

IO 调用:应用程序进程向操作系统内核发起调用。

IO 执行:操作系统内核完成IO操作。

IO模型

阻塞IO

假设应用程序的进程发起 IO 调用,但是如果内核的数据还没准备好的话,那应用程序进程就一直在阻塞等待,一直等到内核数据准备好了,从内核拷贝到用户空间,才返回成功提示,此次 IO 操作,称之为阻塞 IO

阻塞 IO 比较经典的应用就是阻塞 socket、Java BIO。阻塞 IO 的缺点就是:如果内核数据一直没准备好,那用户进程将一直阻塞,浪费性能,可以使用非阻塞IO 优化。

非阻塞 IO

非阻塞 IO 模型,简称 NIO,Non-Blocking IO。它相对于阻塞 IO,虽然大幅提升了性能,但是它依然存在性能问题,即频繁的轮询,导致频繁的系统调用,同样会消耗大量的 CPU 资源。可以考虑 IO 复用模型,去解决这个问题。

非阻塞 IO 的流程如下:

1、应用进程向操作系统内核,发起 recvfrom( )读取数据。

2、操作系统内核数据没有准备好,立即返回 EWOULDBLOCK 错误码。

3、应用程序进程轮询调用,继续向操作系统内核发起 recvfrom 读取数据。

4、操作系统内核数据准备好了,从内核缓冲区拷贝到用户空间。

5、完成调用,返回成功提示。

IO多路复用

IO复用模型核心思路:系统给我们提供一类函数,它们可以同时监控多个fd的操作,任何一个返回内核数据就绪,应用进程再发起recvfrom系统调用

文件描述符 fd(File Descriptor),它是计算机科学中的一个术语,形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

IO多路复用之select函数

应用进程通过调用select函数,可以同时监控多个fd,在select函数监控的fd中,只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时应用进程再发起recvfrom()请求去读取数据。

select函数缺点:

监听的IO最大连接数有限,select 函数返回后,是通过遍历 fdset,找到就绪的描述符 fd。(仅知道有 I/O 事件发生,却不知是哪几个流,所以遍历所有流). 因为存在连接数限制,所以后来又提出了poll

IO多路复用之epoll

它采用事件驱动来实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P74JCRkI-1691206180577)(C:\Users\ycj\AppData\Roaming\Typora\typora-user-images\1691205268889.png)]
epoll 先通过 epoll_ctl()来注册一个 fd(文件描述符),一旦基于某个 fd 就绪时,内核会采用回调机制,迅速激活这个 fd,当进程调用 epoll_wait()时便得到通知。这里去掉了遍历文件描述符的坑爹操作,而是采用监听事件回调的机制。 这就是 epoll 的亮点。

总结一下 select、poll、epoll 的区别
在这里插入图片描述
IO 模型之信号驱动模型

信号驱动不再用主动询问的方式去确认数据是否就绪,而是向内核发送一个信号,然后应用用户进程可以去做别的事,不用阻塞。当内核数据准备好后,再通 过信号通知应用进程,数据准备好后的可读状态。应用用户进程收到信号之后,立即调用 recvfrom,去读取数据。

异步 IO(AIO asynchronous IO)

前面讲的 BIO,NIO 和信号驱动,在数据从内核复制到应用缓冲的时候, 都是阻塞的,因此都不算是真正的异步。AIO 实现了 IO 全流程的非阻塞,就是应用进程发出系统调用后,是立即返回的,但是立即返回的不是处理结果,而是表示提交成功类似的意思。等内核数据准备好,将数据拷贝到用户进程缓冲区,发送信号通知用户进程 IO 操作执行完毕。

异步 IO 的优化思路很简单,只需要向内核发送一次请求,就可以完成数据状态询问和数据拷贝的所有操作,并且不用阻塞等待结果。日常开发中,有类似思想的业务场景:比如发起一笔批量转账,但是批量转账处理比较耗时,这时候后端可以先告知前端转账提交成功,等到结果处理完,再通知前端结果即可。

IO模型

阻塞I/O模型 同步阻塞

非阻塞I/O模型 同步非阻塞

I/O多路复用模型 同步阻塞

信号驱动I/O模型 同步非阻塞

异步IO(AIO)模型 异步非阻塞

Java NIO 概述

阻塞 IO(Blocking I/O BIO)

通常在进行同步 I/O 操作时,如果读取数据,代码会阻塞直至有可供读取的数据。同样,写入调用将会阻塞直至数据能够写入。

传统的 Server/Client 模式服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求。

这种模式带来的一个问题就是线程数量的剧增,大量的线程会增大服务器的开销。大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池线程的最大数量,这由带来了新的问题,如果线程池中有 100 个线程,而有 100个用户都在进行大文件下载,会导致第101个用户的请求无法及时处理,即便第101个用户只想请求一个几KB大小的页面。

非阻塞( non-blocking IO NIO)

核心思想

NIO 中非阻塞 I/O 调用不会被阻塞,核心是注册感兴趣的特定 I/O 事件, 如可读数据到达,新的套接字连接等等,在发生特定事件时,系统再通知我们。NIO 中实现非阻塞 I/O 的核心对象就是 Selector.

Selector 就是注册各种 I/O 事件地方,而且当我们感兴趣的事件发生时, 就是这个对象告诉我们所发生的事件,

当有读或写等任何注册的事件发生时,可以从 Selector 中获得相应的 SelectionKey,同时从 SelectionKey 中可以找到发生的事件和该事件所发生的具体的 SelectableChannel,以获得客户端发送过来的数据。

非阻塞指的是 IO 事件本身不阻塞,但是获取 IO 事件的 select()方法是 需要阻塞等待的.区别是 BIO 会阻塞在 IO 操作上,NIO 阻塞在事件获取上,没有事件就没有IO,从高层次看 IO 就不阻塞了.

NIO

Java NIO 核心部分组成

Channels

Buffers

Selectors

虽然 Java NIO 中除此之外还有很多类和组件,但 Channel,Buffer 和Selector 构成了核心的 API。其它组件,如 Pipe 和 FileLock,只不过是与三个核心组件共同使用的工具类。

Channel

Channel,可以翻译成“通道”。Channel 和 IO 中的 Stream(流)是差不多一个等级的。只不过 Stream 是单向的,譬如:InputStream,OutputStream. 而 Channel 是双向的,既可以用来进行读操作,又可以用来进行写操作。因为Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。

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

NIO 中的 Channel 的主要实现有:

FileChannel:从文件中读写数据

DatagramChannel:通过 UDP 读写网络中的数据

SocketChannel:通过 TCP 读写网络中的数据

ServerSocketChannel:可以监听新进来的 TCP 连接

Buffer

Java NIO 中的 Buffer 用于和 NIO 通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的。

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成 NIO Buffer 对象,并提供了一组方法,用来方便的访问该块内存。

NIO 中的关键的 Buffer 实现有:

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

LongBuffer

FloatBuffer

DoubleBuffer

对数据的读取/写入需要使用 buffer,buffer 本质就是一个数组

常用方法:

ByteBuffer.allocate(1024); 创建字节数据

byteBuffer.flip(); 翻转这个缓冲区,读操作前使用

byteBuffer.clear(); 清除缓存,写操作前使用

Selector

Selector 一般称为选择器。它是 Java NIO 核心组件中的一个,用于检查一个或多个 NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个 channels,也就是可以管理多个网络链接。

使用 Selector 的好处在于:使用更少的线程来就可以来处理通道了,相比使用多个线程,避免了线程上下文切换带来的开销。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JIANG++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值