OSOSOS

1 篇文章 0 订阅

IO

IO 即 Input/Output,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流

-InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流

-OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流

为什么 I/O 流操作要分为字节流操作和字符流操作

-字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时
-如果我们不知道编码类型就很容易出现乱码问题

因此,I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好

字符流默认采用的是 Unicode 编码,我们可以通过构造方法自定义编码

常用字符编码所占字节数

utf8 :英文占 1 字节,中文占 3 字节,unicode:任何字符都占 2 个字节,gbk:英文占 1 字节,中文占 2 字节

字节缓冲流

IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率
在这里插入图片描述
同步和异步
同步和异步指的是:当前线程是否需要等待方法调用执行完毕

比如你调用一个搬运一百块石头的方法:
同步指的是调用这个方法,你的线程需要等待这一百块石头搬完,然后得到搬完了的结果,接着再继续执行剩下的代码逻辑。
异步指的是调用这个方法,立马就直接返回,不必等候这一百块石头还未搬完,可以立马执行后面的代码逻辑,然后利用回调或者事件通知的方式得到石头已经搬完的结果

阻塞和非阻塞
阻塞和非阻塞指的是:当前接口数据还未准备就绪时,线程是否被阻塞挂起
何为阻塞挂起?就是当前线程还处于 CPU 时间片当中,调用了阻塞的方法,由于数据未准备就绪,则时间片还未到就让出 CPU。
所以阻塞和同步看起来都是等,但是本质上它们不一样,同步的时候可没有让出 CPU
而非阻塞就是当前接口数据还未准备就绪时,线程不会被阻塞挂起,可以不断轮询请求接口,看看数据是否已经准备就绪

至此我们可以得到一个结论:
同步&异步指:当数据还未处理完成时,代码的逻辑处理方式不同。

在这个前提下,我们再明确 I/O 操作有两个步骤:

发起 I/O 请求
实际 I/O 读写,即数据从内核缓存拷贝到用户空间
阻塞 I/O 和非阻塞 I/O。按照上文,其实指的就是用户线程是否被阻塞,这里指代的步骤1(发起I/O请求)。

阻塞 I/O,指用户线程发起 I/O 请求的时候,如果数据还未准备就绪(例如暂无网络数据接收),就会阻塞当前线程,让出 CPU。
非阻塞 I/O,指用户线程发起 I/O 请求的时候,如果数据还未准备就绪(例如暂无网络数据接收),也不会阻塞当前线程,可以继续执行后续的任务。
可以发现,这里的阻塞和非阻塞其实是指用户线程是否会被阻塞。

同步 I/O 和异步 I/O。按照上文,我们可以得知这就是根据 I/O 响应方式不同而划分的。

同步 I/O,指用户线程发起 I/O 请求的时候,数据是有的,那么将进行步骤2(实际 I/O 读写,即数据从内核缓存拷贝到用户空间),这个过程用户线程是要等待着拷贝完成。
异步 I/O,指用户线程发起 I/O 请求的时候,数据是有的,那么将进行步骤2(实际 I/O 读写,即数据从内核缓存拷贝到用户空间),拷贝的过程中不需要用户线程等待,用户线程可以去执行其它逻辑,等内核将数据从内核空间拷贝到用户空间后,用户线程会得到一个“通知”。
再仔细思考下,在 I/O 场景下同步和异步说的其实是内核的实现,因为拷贝的执行者是内核,一种是同步将数据拷贝到用户空间,用户线程是需要等着的。一个是通过异步的方式,用户线程不用等,在拷贝完之后,内核会调用指定的回调函数。

如果不理解上面,就只需记住:

同步I/O:指的是用户线程会需要等待步骤 2 执行完毕。
异步I/O:指的是用户线程不需要等待步骤 2 执行。
好了,如果以上的概念你都已经理解了的话,那么平日里我们所说的同步阻塞I/O,同步非阻塞I/O等其实就是把上面的两个步骤合起来看,应该不难理解。

我再简单的总结一下,关于 I/O 的阻塞、非阻塞、同步、异步:

阻塞和非阻塞指的是发起 I/O 请求后,用户线程状态的不同,阻塞I/O在数据未准备就绪的时候会阻塞当前用户线程,而非阻塞 I/O 会立马返回一个错误,不会阻塞当前用户线程。
同步和异步是指,内核的 I/O 拷贝实现,当数据准备就绪后,需要将内核空间的数据拷贝至用户空间,如果是同步 I/O 那么用户线程会等待拷贝的完成,而异步 I/O则这个拷贝过程用户线程该干嘛可以去干吗,当内核拷贝完毕之后会“通知”用户线程。

BIO

BIO情况下,当用户调用read后,用户线程会被阻塞,然后等待两个阶段

-等待CPU把数据从磁盘读到内核缓冲区

-等待CPU把数据从内核缓冲区拷贝到用户缓冲区
在这里插入图片描述在这里插入图片描述

因此应用进程向内核发起I/O请求时,发起调用的线程会一直等待内核返回结果。虽然说可以使用多线程模型,一个请求对应一个线程,但是线程是十分有限和宝贵的。但是无可否认的是能够及时的返回数据,无延迟

NIO

非阻塞的read请求在数据为准备好的情况下返回,可以继续往下执行,然后应用程序会不断地轮询内核,询问数据是否准备好,当数据还没有准备好时,内核立即返回EWOULDBLOCK错误,直到数据准备好之后,内核将数据拷贝到应用程序缓冲区,read请求才获取到结果
在这里插入图片描述
BIO、NIO、AIO定义

1、BIO:一个线程触发IO操作之后必须等待这个IO操作完成,期间不能去做其他工作,一万个客户端就需要一万条线程
  
2、NIO:同步非阻塞,一个线程触发IO操作之后它可以立即返回去做其他工作,但是需要不断轮询去获取结果(这里同步是通过轮询的方式),注意:从内核空间拷贝到用户空间这个过程是同步的

3、AIO:异步非阻塞,一个线程触发IO操作之后它可以立即返回去做其他工作,当io操作完成之后内核系统将会通知该线程

举例:

你就是一个线程

-BIO:你去银行取钱的时候只能排队,在排队期间你不能做任何事情

-NIO:你去银行取钱时,跟大厅经理说轮到我取钱的时候告诉你,你在大厅玩手机,但是你很急,不断地去问经理轮到你没有

-AIO:你想取钱,但是你叫了一群小弟去取,然后你接着做你自己的事,然后让小弟拿到钱之后打电话跟你说就可以

应用场景:

1、BIO:适用于连接数比较小且固定的架构,这种方式对服务器要求比较高;并发局限于应用;jdk1.4以前的唯一选择,但是程序简单易懂

2、NIO:适用于连接数比较多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用;编程比较复杂,jdk1.4开始支持;

3、AIO:适用于连接数比较多且连接比较长(重操作)的架构,比较相册服务器,充分调用OS参与并发操作,编程比较复杂,jdk7开始支持;

后续有时间学习netty再补笔记
https://blog.csdn.net/Youth_lql/article/details/115524052

BIO和NIO

如果是BIO,那么每个IO都需要一个线程去处理,且该线程只做一件事就是处理这个IO,如果此时发生了IO阻塞,那么相应的线程会被挂起,直到IO恢复正常之后再唤醒这个线程,因此线程的阻塞以及切换也带来了额外的开销

而如果是NIO,那么IO对应的线程不受限制,当IO阻塞时线程可以去处理其他事情,当IO恢复正常时继续回来处理IO,而不是干等

BIO及NIO的传输过程

1、BIO是面向流的,而且建立的通道是单向的,分为输入流和输出流两个通道

在这里插入图片描述
2、NIO,而NIO是面向缓冲区的,输入和输出共享一个通道,通道负责传输,缓冲区负责存储
在这里插入图片描述
因此BIO跟NIO的区别可以简单概括如下

1、BIO是单向通道且是面向流,而NIO是双向通道且是面向缓冲区的

2、BIO是阻塞IO,而NIO是非阻塞IO

传统IO文件拷贝(传统的IO交流

首先需要了解一下操作系统里的虚拟空间分类,分为用户空间和内核空间,用户空间是给用户进程使用的,而内核空间是专门给操作系统底层使用的

在这里插入图片描述
1、首先内核空间去硬盘里读取这张照片的字节数据并将其保存在内核缓冲区,用户态切换到内核态
2、然后操作系统将内核缓冲区里的图片字节数据拷贝到用户进程缓冲区,内核态切换到用户态
3、然后将用户进程缓冲区里的字节数据复制到内核缓冲区,用户态切换到内核态
4、将内核缓冲区里的字节数据粘到磁盘的另一个位置,内核态切换到用户态

因此传统文件拷贝会涉及到四次复制以及四次上下文切换

复制一张照片到磁盘另一个位置一共需要拷贝四次,其中由两次是内核空间之间的数据拷贝,这涉及到用户态跟内核态的上下文切换,需要cpu参与进来,但其实这两次拷贝是无意义的

而NIO可以减轻CPU的压力,运用了零拷贝技术

零拷贝技术

零拷贝技术指的是内核空间和用户空间之间的零次拷贝,在NIO中,零拷贝是通过用户空间和内核空间的缓冲区共享一块物理内存实现的

在这里插入图片描述
NIO拷贝文件过程

1、用户空间里的进程通过调用read请求读取文件,此时进行第一次上下文切换,切换到内核空间,数据从硬盘拷贝到内核空间共享缓冲区,第一次拷贝

2、读取完毕,内核空间切换到用户空间,第二次上下文切换,由于内核空间共享缓存区,因此不需要将图片字节数据拷贝到用户缓冲区

3、当用户空间里的进程发起write请求写数据到磁盘上时,进行第三次上下文切换切换到内核空间,此是需要将内核空间共享缓冲区里的数据拷贝到内核socket缓冲区,第二次数据拷贝

4、将socket缓冲区里的内容写到磁盘上,第三次数据拷贝,返回到用户空间,第四次上下文切换

因此可以得知cpu零拷贝的优点:降低cpu的压力,可以避免用户空间和内核空间之间的拷贝工作

选择器

所有IO通道都会注册到选择器上,选择器会轮询这些通道的IO状态,只有状态完全满足时选择器才会让服务器去处理这些。不同客户端对服务器的IO连接不会因为其他客户端的未就绪而阻塞,即同步非阻塞

以下是BIO跟NIO的区别
在这里插入图片描述
看一下selector选择器里的一些方法,可以发现一个叫做select以及他的重载方法是会阻塞的
在这里插入图片描述
当选择器轮询这些没有就绪的通道,操作系统有两种做法

1、一直等待直到一个就绪的通道,再返回给用户进程
2、立即返回一个错误状态码给用户进程,让用户进程继续运行不会阻塞

其实NIO并不是严格上的非阻塞IO,阻塞IO里的功能NIO也有,可以把NIO叫做new-IO,他提供了Non-Blocking 的功能以及保留原有Blocking IO功能

选择键

如何识别就绪的通道属于那种状态?选择键,在选择器轮询到有就绪通道时,会返回这些通道的就绪选择键,通过选择器可以获取到通道进行操作

SelectionKey.OP_READ:套接字准备好进行读操作
SelectionKey.OP_WRITE:套接字准备好进行写操作
SelectionKey.OP_ACCEPT:服务器套接字通道接受其他通道
SelectionKey.OP_CONNECT:套接字通道准备完成连接

IO多路复用

NIO同步非阻塞如果发现不能读/写,就会通过轮询的方式去询问是否可读或者可写,但这样可能会造成一个问题就是轮询了一圈发现一个可读/可写的都没有,这样就是白白耗费cpu资源了。通过IO多路复用可以解决这个问题,它的具体题先有select、poll、epoll

整体流程:用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作

特点:通过一种机制能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个变为可读就绪状态,select()/poll()函数就会返回
在这里插入图片描述
select、poll:
-应用程序首先发起 select 系统调用,传入要监听的文件描述符集合
-内核遍历应用程序传入的 fd (文件标识符)集合,如果遍历完一遍后发现没有就绪的 fd 则用户进程会进入阻塞状态,如果有就绪的 fd 则会对就绪的 fd 打标,然后返回
-应用程序遍历 fd 集合,找到就绪的 fd,进行相应的事件处理

优点:不需要每个 FD 都进行一次系统调用,导致频繁的用户态内核态切换
缺点:每次需要将 FD 从用户态拷贝到内核态,无论复制是否有意义;不知道具体是哪个文件描述符就绪,需要遍历全部文件描述符

epoll:直接将 fd 集合维护在内核中,通过红黑树来高效管理 fd 集合,同时维护一个就绪列表,当 fd 就绪后会添加到就绪列表中,当应用空间调用 epoll_wait 获取就绪事件时,内核直接判断就绪列表即可知道是否有事件就绪,可以精确知道哪个fd准备就绪;而select、poll做不到

笔记:https://blog.csdn.net/v123411739/article/details/124699602

信号驱动I/O

无论是BIO还是NIO的read 或者write。例如调用read时,从内核空间拷贝数据到用户空间这个过程都是同步的。如果内核的拷贝效率不高,即从磁盘里拷贝数据到内核空间这个过程比较慢的话,那么这个同步耗费的时间就会比较长了

信号驱动I/O属于一种半异步的程序,在使用信号驱动 I/O 时,当数据准备就绪后,内核通过发送一个 SIGIO 信号通知应用进程,应用进程就可以开始读取数据了,不会让程序一直在阻塞或者轮询
在这里插入图片描述
第一阶段(非阻塞):

①:进程使用socket进行信号驱动I/O,建立SIGIO信号处理函数,向内核发起系统调用,内核在未准备好数据报的情况下返回一个信号给进程,此时进程可以继续做其他事情
②:内核将磁盘中的数据加载至内核缓冲区完成后,会递交SIGIO信号给用户空间的信号处理程序
第二阶段(阻塞):

③:进程在收到SIGIO信号程序之后,进程向内核发起系统调用(recvfrom)
④:内核再将内核缓冲区中的数据复制到用户空间中的进程缓冲区中(真正执行IO过程的阶段),直到数据复制完成
⑤:内核返回成功数据处理完成的指令给进程;进程在收到指令后再对数据包进程处理;处理完成后,此时的进程解除不可中断睡眠态,执行下一个I/O操作
在这里插入图片描述
异步IO

真正的异步 I/O 是内核数据准备好 和 数据从内核态拷贝到用户态 这两个过程都不用等待。

当我们发起 aio_read (异步 I/O) 之后,就立即返回,内核自动将数据从内核空间拷贝到用户空间,这个拷贝过程同样是异步的,内核自动完成的,和前面的同步操作不一样,应用程序并不需要主动发起拷贝动作

异步 I/O 最重要的一点是从内核缓冲区拷贝数据到用户态缓冲区的过程也是由系统异步完成,应用进程只需要在指定的数组中引用数据即可。异步 I/O 与信号驱动 I/O 这种半异步模式的主要区别:信号驱动 I/O 由内核通知何时可以开始一个 I/O 操作,而异步 I/O 由内核通知 I/O 操作何时已经完成
在这里插入图片描述
在这里插入图片描述

Refactor

refactor即为IO多路复用监听事件,主要由Reactor、Acceptor、Handler三个对象组成

-Reactor:负责监听和分发事件
-Acceptor:创建连接并创建一个能处理后续响应事件的Handler对象
-Handler:处理业务,一般流程为:read、处理业务、send

单Reactor单进程/单线程

工程流程:Refactor通过select系统(IO多路复用接口)来对事件进行监听,然后再通过dispatch来进行分发,如果是建立连接的事件则将事件分发acceptor对象进行处理,否则将交给handler对象进行处理
在这里插入图片描述优点:因为全部工作都在同一个进程内完成,所以实现起来比较简单,不需要考虑进程间通信,也不用担心多进程竞争

缺点:因为只有一个进程,无法充分利用 多核 CPU 的性能,Handler对象在业务处理时,整个进程是无法处理其它连接事件,如果业务处理耗时比较长,那么就造成响应的延迟

使用场景:单Reactor单进程的方案不适用计算机密集型的场景,只适用于业务处理非常快速的场景。如Redis,Redis 业务处理主要是在内存中完成,操作的速度是很快的,性能瓶颈不在 CPU 上

单Reactor多线程/多进程

该方案能克服克服单 Reactor 单线程/单进程方案的缺点

工作流程:Reactor通过select系统来监听事件,然后通过dispatch来进行事件的分发。建立连接事件分发给acceptor对象,业务处理事件分发给handler对象。但是handler将不再进行业务处理,把业务处理交给了子线程里的Processor进行处理。因此handler的处理流程为:read,交给子线程里的Processor处理,处理完Processor对象将结果返回给handler对象,然后最后再send响应给client
在这里插入图片描述
优点:能够充分利用多核 CPU 的能力

缺点:带来了多线程竞争资源问题(如需加互斥锁解决;单Reactor 的模式还有个问题,因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能瓶颈

多Reactor多进程/多线程

要解决单Reactor 的问题,就是将单Reactor 实现成多Reactor,这样就产生了 多Reactor多进程/线程方案

工作流程:主线程中的MainReactor,通过select来监控链接创建事件,通过acceptor对象中的accpet方法来获取连接,然后将连接发给子线程。子线程中的SubReactor将通过select继续监听该连接,并创建handler对象。如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应,Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程

优点:主线程只负责接收创建新的连接,即初始化socket,子进程的 Reactor 来 accept 连接,并完成后续的业务处理

应用场景:netty、nginx

Proactor

-待补充

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值