在之前的文章中,已经发布了常见的面试题,这里我花了点时间整理了一下对应的解答,由于个人能力有限,不一定完全到位,如果有解答的不合理的地方还请指点,在此谢过。

本文主要描述的是java面试中出现频率较高的IO。看过jdk源码的同学可能知道,java的IO模型分为两种,传统的IO和NIO。传统的IO就是我们平时常用的BIO(Blocking IO),它的包名是java.io;而NIO(NoBlocking IO)的包名是java.nio。网络io其实是面试中常见的问题,掌握这个对我们的面试会有很大的帮助。下面我们就io的常见面试题进行解答说明。

能简单说明一下你了解的网络IO模型么?

在回答这个问题之前,先来看下操作系统在完成一次IO的过程需要经过哪些操作。对于操作系统而言,外部设备的操作都可以看作是对文件的操作,当我们读写一个文件的时候,系统内核会返回一个文件描述符(file descriptor),该文件描述符指向内核中的一个结构体,我们操作文件就是对该结构体进行操作。我们都知道,在Linux系统中,内存被分为内核态和用户态。Linux的内核态管理所有接入的硬件资源,并且会提供相关的函数供用户态调用。在了解了这些操作系统上的概念之后,我们以读操作来描述一下一个IO过程。当我们的程序在调用读操作的时候,操作系统会发起read指令,同时在内核态中创建一个文件描述符,等待把读的数据放入到文件描述符指定的结构体缓冲区后,然后将缓冲区的数据读回到用户态的内存中,这样就完成了一次IO的操作。我们可以用下面的示意图来描述一下io的过程。

基本上一个io的过程需要经过上图的两个步骤:

  1. 等待数据准备,将硬件读取数据到指定的内核缓冲区中(过程2)
  2. 将缓冲区的内容读回到用户态的应用程序中(过程1)

当然了写的过程就是相反的,把我们的数据写到硬件设备中。有了上面对于的知识,下面我们解释几个io中名词。

阻塞和非阻塞:阻塞和非阻塞的区别在上述描绘的两个阶段中,如果第一阶段就阻塞在那里等待数据的话,我们称为阻塞,否则是非阻塞。

同步和异步:同步和异步的区别在于如果两个阶段都不需要应用程序阻塞等待的话,那么称为异步,否则称为同步。

可能看完上面的基本概念之后会有些模糊,那么现在我们引用一下Unix网络编程中描述的5种网络io模型。

阻塞io

阻塞io是应用程序在调用io操作时,应用程序会一直等待内核操作完成,直到把数据返回回来。其特点在于两个阶段都阻塞了。

非阻塞io

非阻塞io是在第一阶段发送请求之后,会立即得到数据是否准备好了,然后应用程序不断发起请求询问数据准备的情况,直到数据准备完成。进入第二阶段阻塞式拷贝回用户态。其特点是:第一阶段不阻塞,不过需要用户程序不断轮询数据准备的情况。

Io多路复用

Io多路复用基于操作系统提供了select的分离函数。用户在第一阶段将socket添加到select中,然后阻塞等待select系统调用返回,当数据到达时,socket被激活然后发起read操作。其特点是:对于单个socket来说和阻塞io基本类似,甚至还更差点,但其优势在于用户可以在一个线程中添加多个socket请求,换句话说用户可以在一个线程内请求和处理多个io请求的目的。我们知道io的操作中本身就是io耗时,而cpu使用率较低,如果我们将多个io的请求放到一个线程上的话,对cpu来说完全是没有任何问题的,这也是其优势的地方。

Io多路复用是最长问的一个io网络模型,也是现在使用最多的场景。其实现方式有select,poll和epoll,这三者的区别也是面试中的常问题目之一。

信号驱动io

信号驱动io是在第一步的时候,用户程序会向内核注册一个 信号处理函数,然后用户进程返回,当内核数据就绪时,向该进程发送一个信号,用户进程在信号处理函数中读取数据。其特点是:第一个过程是不阻塞的,第二个过程依然是阻塞的,读取数据的过程依然是用户来完成。

异步io:

异步io是用户程序发起一个aio_read的函数操作之后,该函数会告诉内核描述符,缓冲区的指针,缓冲区的大小等信息,然后会立即返回,而内核会将数据准备完成,并且将数据拷贝到用户态内存中,并且通知程序完成了操作。在Linux操作系统中,并没有实现真正意义上的异步io,因为异步io将很多事情都交给了内核去处理,其处理速度取决于用户需要操作的数据的大小,如果数据量很大,内核速度很慢,如果数据量很小,那io多路已经足够快了。而Windows系统的iocp实现方式是真正意义上实现了异步io。 其特点:真正的实现了异步。

io多路复用的select、poll、epoll的区别是什么?

Io多路复用是现在高性能的组件的核心网络io模型,一般我们看一个中间件的话,基本上都会使用到io多路复用。那么下面我们看下io多路复用的三种调用方式:

Select:

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);typedef struct{long int fds_bits[32];//每一位可以代表一个文件描述符,所以是1024个}fd_set;

参数值:

maxfdp1 待测试文件描述符数量+1。

*readset , *writeset ,  *exceptset 分别是内核测试读、写和异常条件的文件描述符集合。

*timeout 任何一个文件描述符准备就绪的超时时间。

返回值:已经就绪的文件描述符的数量

Select有几个不足之处:

  1. 每次调用select的时候,都会把fd_set的几个参数从用户态到内核态的拷贝
  2. 内核会线性遍历所有传入的文件描述符是否有准备就绪
  3. 为了限制内核的开销,fd_set的数量被限制在1024以内

Poll:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);typedef struct pollfd {int fd;                         // 文件描述符short events;                   // 文件描述符的监听事件short revents;                  // 文件描述符fd上正在发生的事件} pollfd_t;

参数值:

*fds:文件描述符的链表存储结构

nfds:fds的总数量

Timeout:超时时间

Poll是select的改进版,基本原理和select类似,其改进的有点在于:

修改了之前fd_set的方式来描述文件描述符,而使用pollfd方式,之前fd_set是一个数组结构,而pollfd是一个链表的结构,从而没有1024的限值。

Epoll:

int epoll_create(int size);//创建一个文件描述符为size的epollint epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//注册要监听的事件类型int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);//等待事件就绪函数

Epoll是Linux操作系统出现的一种基于事件驱动的io模式,其原理还是io多路复用,不过其改进点在于以下几方面:

  1. epoll在内核里面使用事件表的方式记录了文件描述符相关信息(底层是基于mmap保存),这样就只需要拷贝一次用户态到内核态。
  2. Epoll底层使用红黑树的数据结构存储文件描述符的信息
  3. Epoll采用事件回调机制,注册fd的时候会添加关注的事件,一旦fd就绪就会主动通知内核,从而避免每次线性扫描

Epoll的两种触发方式有什么区别?

Epoll有水平触发(LT)和边沿触发(ET)两种方式。

水平触发:是默认的触发方式,当epoll_wait检测到某文件描述符事件就绪并通知应用程序时,应用程序可以选择不立即处理;下次调用epoll_wait时,会再次通知此事件。其核心在于只要是就绪态就会通知。

边沿触发:当epoll_wait检测到某文件描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。其核心在于只有未就绪到就绪的上升沿会触发通知。

从上面可以知道et可以减少通知的次数,但对应用程序要求高,必须立马处理,否则可能会丢消息。而lt则是通知的次数变多,但是不会丢消息,在绝大部分的情况下。我们都使用lt的模式。

有了解过reactor和proactor模型么?

如果学过netty的同学,多多少少会知道reactor模型,毕竟netty整个的设计模式就是reactor模型的经典设计。下面我们就来聊聊这两种io设计模式。

Reactor模式是同步io的一种常用设计方式,其关注的是事件就绪,基于事件驱动,特别适合处理大量io事件。一般reactor模型中有以下三种角色:

Reactor:监听和分配事件,将io事件分配handler。

Acceptor:处理客服端请求,并转发到reactor中进行处理。

Handler:阻塞的完成channel的读入和写出,完成主要的逻辑。

单线程的reactor模型如下几个组件构成:

然后再看下netty的reactor模型:

Bossgroup和workgroup是reactor组件,eventloop绑定的thread进行相应的handler事件处理。

Proactor模型是基于异步io实现的,又被称为主动器模式,其关注的是事件完成。一般也有以下几个角色:

Proactor initiator:创建Proactor和handler,并将两者通过异步处理器注册到内核中。

异步处理器:负责处理注册请求,并完成io操作,完成后通知Proactor。

Proactor:Proactor负责回调不同的handler进行业务处理。

Handler:业务处理器,处理业务逻辑。

其主要的实现模型图如下:

其实reactor和Proactor的核心区别在于同步io和异步io的设计。

说说你对java整个io包的理解,java在设计io的时候使用了哪两种设计模式?

在Java中,将不同的终端的输入输出源抽象为流的概念,通过这种方式可以将不同终端的数据进行交互。Java将传统的流类型都放在java.io包中,一般我们将IO以以下三种方式进行分类。

  1. 按照流的方向:输入流和输出流

输入输出流是针对程序运行的内存而言的,从内存输出到其他介质上称为输出流,从其他介质输入到程序内存中称为输入流。一般以inputstream/reader结尾的称为输入流,outputstream/writer结尾的称为输出流。

  1. 按照操作单元分:字节流和字符流

字节流是针对一个字节一个字节进行操作的,字符流最小的操作单元是一个字符。字节流以inputstream/outputstream结尾,字符流以writer/reader结尾。字符流和字节流的转换使用适配器模式进行。

  1. 按照是否可以直接连接一个介质来划分:节点流和处理流

节点流是指可以直接连接在一个介质上进行输入输出的,而处理流是将节点流进行包装而具有新的功能,节点流也被称为低级流,处理流因为有了新的功能特性(当然了也包含了原来的特性)被称为高级流。这里jdk使用装饰器模式进行。

整个io包中的可以如下分类:

NIO的三大组件了解么?说说channel和stream的区别?

在nio包中,围绕着三个核心的组件进行,分别是selector,channel和buffer。selector是事件选择器,通过监听不同的响应事件将选择不同的channel中,所以selector和channel是一对多的关系。Channel和buffer是一起的,我们通过channel将数据写入buffer,也需要通过channel将数据从buffer中读出,channel和buffer是一对一的关系。下面我们通过一个模型图表示它们三者之间的联系。

Channel和stream有点类似,但是又有实际的区别:

  1. channel是双向的,stream是单向的
  2. Channel可以是异步的读写
  3. Stream是io里面的概念,是一个流的概念,而channel是nio里面的概念,是一个通道的概念。

Java的io是平时经常使用的,也是面试中长考察的知识点,尤其是网络io。有时间和兴趣的同学可以掌握一下netty的框架,对后续了解好多架构的底层网络通信模块都有帮助。

本文的内容就这么多,如果你觉得对你的学习和面试有些帮助,帮忙点个赞或者转发一下哈,谢谢。

想要了解更多java内容(包含大厂面试题和题解)可以关注公众号,也可以在公众号留言,帮忙内推阿里、腾讯等互联网大厂哈

Java基础篇--IO相关推荐

  1. 【程序员养成之路】Java基础篇 8-流进流出的IO流(二)

    以下内容若有误,欢迎私信我或在下方留言,谢谢^_− 目录 IO流(二) 1.特殊操作流 1.1 标准流 1.2 打印流 1.3 对象序列化流 1.4 Properties 拓展1:比较字节流和字节缓冲 ...

  2. 菜鸟学习笔记:Java基础篇7(包装类、时间相关类、文件类、异常处理类)

    菜鸟学习笔记:Java其他常用类 基本数据类型包装类 时间处理和文件处理相关类 Date时间类 SimpleDateFormat Calendar日历类 文件类 异常机制 异常的概念 Java异常处理 ...

  3. 《Java 后端面试经》Java 基础篇

    <Java 后端面试经>专栏文章索引: <Java 后端面试经>Java 基础篇 <Java 后端面试经>Java EE 篇 <Java 后端面试经>数 ...

  4. 高频面试真题答案 -java后端 -java基础篇

    原贴 2022届秋招高频面试真题汇总,千题奉送!!!- 后端篇_笔经面经_牛客网 整理答案: 类加载机制 47 双亲委派机制 24 new一个对象的过程 4 java程序是如何运行起来的? 1 jvm ...

  5. java基础篇_java基础篇1

    JAVA基础篇1 注释 单行注释 //这是一个单行注释,由两个斜杠组成,不能嵌套多行注释 多行注释 /*这是一个 多行注释 ,//里面不能嵌套多行注释, 但是可以嵌套单行注释*/ 文档注释 /**ja ...

  6. 你所需要的java基础篇深入解析大汇总

    java基础篇深入解析大总结 java基础(一) 深入解析基本类型 java基础(二) 自增自减与贪心规则 java基础(三) 加强型for循环与Iterator java基础(四) java运算顺序 ...

  7. Java学习笔记(7)——Java基础之IO多线程网络思维导图

    Java面向对象学习笔记之:包括IO(字节流,字符流,节点流,处理流).线程(线程创建,线程控制,线程同步).网络(TCP Scoket,  UDP Scoket)(全屏观看Java学习笔记(7)-- ...

  8. Java基础篇4——数组

    Java基础篇4--数组 1.数组的概念 当需要在Java程序中记录单个数据内容时,则声明一个变量即可 当需要在Java程序中记录多个类型相同的数据内容时,则声明一个一维数 组即可,一维数组本质上就是 ...

  9. Java基础篇3——流程控制

    Java基础篇3--流程控制 1.顺序结构 正常代码的流程即是顺序流程 2.分支结构 2.1.if-else分支 if(条件表达式) {语句块1; } if(条件表达式) {语句块1; } else ...

  10. Java基础篇2——运算符

    Java基础篇2--运算符 1.运算符 1.1.算数运算符 +表示加法运算符 -表示减法运算符 *表示乘法运算符 /表示除法运算符 %表示取余运算符 1.2.关系运算符 所有以关系运算符作为最终运算的 ...

最新文章

  1. python 删除list 里面的一个空集合
  2. stm32双向可控硅调压程序_单向可控硅和双向可控硅的测量与模块测试
  3. TensorRT trtexec的用法
  4. 使用Docker打包发布Django应用
  5. 【Let‘s Go】Go语言入门篇
  6. 解决XML中报“cvc-complex-type.2.4.a: Invalid content was found starting with element ”错误
  7. 分布式之数据库和缓存双写一致性方案解析!
  8. 热传导/物质扩散算法应用于推荐
  9. 《中国人工智能学会通讯》——11.64 基于成对约束的属性特征选择
  10. 无损链接分解_一点都不能少!伯克利研究人员提出深度学习锻造无损数据压缩新方法...
  11. 数据结构之内部排序三
  12. 80 多个免费编程字体,你喜欢哪种?
  13. Python实现google翻译
  14. 展开操作符:一家人就这么被拆散了
  15. 云计算时代运维的出路在哪?
  16. airpods链接mac弹窗_AirPods怎么连接Mac AirPods连接Mac教程
  17. 月度行业报告模板说明
  18. CNN+LSTM 的模型结合(keras代码实现)
  19. 机器学习笔记——回归(Regression)
  20. 什么是CSM(Certified Scrum Master) 敏捷认证

热门文章

  1. Storm概念详解和工作原理,topology、spout、bolt的细节和API讲解之一
  2. Jexi设计 (1) Lexi研究
  3. 把edge默认上网页面改为百度
  4. t台式计算机如何安装2个硬盘,台式机械硬盘怎么安装?机械硬盘安装图解教程(SATA固态可参考)(2)...
  5. 医院排队叫号系统源码
  6. AS3动画效果公式,常用处理公式代码,基本运动公式,三角公式
  7. 基于python的智能风扇设计_基于单片机的智能风扇的设计与实现
  8. 瞎扯数学分析:微积分
  9. 工作之外的闲暇时光(玩魔方)
  10. 华硕主板无盘启动bios设置_华硕主板开机怎么进入bios_华硕主板bios设置U盘启动方法...