搞定Java NIO:NIO面试问题梳理

在搞IO之前,先搞清楚这个几个概念。

  • 同步与异步(synchronous/asynchronous):同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系
  • 阻塞与非阻塞:在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如ServerSocket新连接建立完毕,或者数据读取、写入操作完成;而非阻塞则是不管IO操作是否结束,直接返回,相应操作在后台继续处理

1、同步和异步的概念:实际的I/O操作

  • 同步是用户线程发起I/O请求后需要等待或者轮询内核I/O操作完成后才能继续执行
  • 异步是用户线程发起I/O请求后仍需要继续执行,当内核I/O操作完成后会通知用户线程,或者调用用户线程注册的回调函数

2、阻塞和非阻塞的概念:发起I/O请求

  • 阻塞是指I/O操作需要彻底完成后才能返回用户空间
  • 非阻塞是指I/O操作被调用后立即返回一个状态值,无需等I/O操作彻底完成

一、IO、NIO 和 AIO

关于Java中的IO,网络通讯模型主要分三种:IO、NIO和AIO。

适用场景

  • BIO,适用于连接较少,对服务器资源消耗很大,但是编程简单。是同步阻塞的。
    • 举例:你到餐馆点餐,然后在那儿等着,什么也做不了,只要饭还没有好,就要必须等着
  • NIO,使用于连接数量比较多且连接时间比较短的架构,比如聊天服务器,编程比较复杂。是同步非阻塞的
    • 举例:你到餐馆点完餐,然后就可以去玩儿了,玩一会儿就回饭馆问一声,饭好了没。
  • AIO,适用于连接数量多而且连接时间长的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂。是异步非阻塞的。
    • 举例:饭馆打电话给你说,我们知道你的位置,待会儿给您送来,你安心的玩儿就可以了。类似于外卖。

1、IO(同步阻塞)

传统的网络通讯模型,就是BIO,同步阻塞IO, 其实就是服务端创建一个ServerSocket, 然后就是客户端用一个Socket去连接服务端的那个ServerSocket, ServerSocket接收到了一个的连接请求就创建一个Socket和一个线程去跟那个Socket进行通讯。接着客户端和服务端就进行阻塞式的通信,客户端发送一个请求,服务端Socket进行处理后返回响应,在响应返回前,客户端那边就阻塞等待,上门事情也做不了。 这种方式的缺点, 每次一个客户端接入,都需要在服务端创建一个线程来服务这个客户端,这样大量客户端来的时候,就会造成服务端的线程数量可能达到了几千甚至几万,这样就可能会造成服务端过载过高,最后崩溃死掉。

2、NIO(同步非阻塞,JDK1.4)

NIO是一种同步非阻塞IO, 基于Reactor模型来实现的。其实相当于就是一个线程处理大量的客户端的请求,通过一个线程轮询大量的channel,每次就获取一批有事件的channel,然后对每个请求启动一个线程处理即可。这里的核心就是非阻塞,就那个selector一个线程就可以不停轮询channel,所有客户端请求都不会阻塞,直接就会进来,大不了就是等待一下排着队而已。这里面优化BIO的核心就是,一个客户端并不是时时刻刻都有数据进行交互,没有必要死耗着一个线程不放,所以客户端选择了让线程歇一歇,只有客户端有相应的操作的时候才发起通知,创建一个线程来处理请求。

3、AIO(NIO2,异步非阻塞,JDK1.7)

对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。但是对AIO来说,则更加进了一步,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。因此AIO是不会阻塞的,此时我们的业务逻辑将变成一个回调函数,等待IO操作完成后,由系统自动触发。

与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2,主要在Java.nio.channels包下增加了下面四个异步通道:

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel

AIO是异步非阻塞IO,基于Proactor模型实现。 每个连接发送过来的请求,都会绑定一个Buffer,然后通知操作系统去完成异步的读,这个时间你就可以去做其他的事情,等到操作系统完成读之后,就会调用你的接口,给你操作系统异步读完的数据。这个时候你就可以拿到数据进行处理,将数据往回写,在往回写的过程,同样是给操作系统一个Buffer,让操作系统去完成写,写完了来通知你。这俩个过程都有buffer存在,数据都是通过buffer来完成读写。,

二、NIO 和 IO 的区别

NIO的三个主要特点:面向缓冲、同步非阻塞和多路复用。

1、面向缓冲

传统IO是面向流的,NIO是面向缓冲的。传统IO是每次从流中读一个或多个字节,直到读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。NIO是将数据读取到一个她稍后处理的缓冲区,需要时可在缓冲区中前后移动,增加了处理过程中的灵活性。

2、同步非阻塞

传统IO的流是阻塞的,当一个线程调用read() 或 write()时,该线程被阻塞,直到数据完成读取或写入完成,此期间该线程不能再干任何事情了。NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。

3、选择器(多路复用)

多路复用是指使用单线程也可以通过轮询监控的方式实现多线程类似的效果。简单的说就是,通过选择机制,使用一个单独的线程很容易来管理多个通道。

三、NIO 的核心概念

NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程。NIO的三大核心组件:通道(Channel)、缓冲(Buffer)、选择器(Selector)

1、通道(Channel)

Channel是传统IO中的Stream(流)的升级版,是一个对象,可以通过它读取和写入数据。Stream是单向的、读写分离,Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。Channel和Buffer的关系可以用下图来表示,

2、缓冲(Buffer)

Buffer可以理解为一块内存区域,可以写入数据,并且在之后读取它。这块内存被包装成NIO buffer对象,它提供了一些方法来更简单地操作内存。

使用 Buffer 读写数据一般遵循以下四个步骤:

  • 写入数据到 Buffer;
  • 调用 flip() 方法;
  • 从 Buffer 中读取数据;
  • 调用 clear() 方法或者 compact() 方法。

当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip() 方法将 Buffer 从写模式切换到读模式。在读模式下,可以读取之前写入到 Buffer 的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear() 或 compact() 方法。clear() 方法会清空整个缓冲区。compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

buffer有3个属性需要熟悉以理解buffer的工作原理:

  • 容量(Capacity):缓冲区能够容纳的数据元素的最大数量。容量在缓冲区创建时被设定,并且永远不能被改变。
  • 上界(Limit):写模式中等价于buffer的大小,即capacity;读模式中为当前缓冲区中一共有多少数据,即可读的最大位置。
  • 位置(Position):下一个要被读或写的元素的位置。初始化为0,buffer满时,position最大值为capacity-1。

3、选择器(Selector)

选择器(Selector)可以实现一个单独的线程来监控多个注册在她上面的信道(Channel),通过轮询的选择机制,实现多路复用的效果。

NIO多路复用的主要步骤和元素:

  • 首先,通过 Selector.open() 创建一个 Selector,作为类似调度员的角色。
  • 然后,创建一个 ServerSocketChannel,并且向 Selector 注册,通过指定 SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求。
  • 注意,为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册操作是不允许的,会抛出 IllegalBlockingModeException 异常。
  • Selector 阻塞在 select 操作,当有 Channel 发生接入请求,就会被唤醒。
  • 在 具体的 方法中,通过 SocketChannel 和 Buffer 进行数据操作

IO 都是同步阻塞模式,所以需要多线程以实现多任务处理。而 NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。

四、常见的NIO框架

1、Mina 

Mina是 Apache组织一个较新的项目,它为开发高性能和高可用性的网络应用程序提供了非常便利的框架。当前发行的 Mina 版本2.04支持基于 JavaNIO 技术的 TCP/UDP 应用程序开发、串口通讯程序,Mina 所支持的功能也在进一步的扩展中。

2、Netty 

Netty是一款异步的事件驱动的网络应用框架和工具,用于快速开发可维护的高性能、高扩展性协议服务器和客户端。也就是说,Netty是一个NIO客户端/服务器框架,支持快速、简单地开发网络应用,如协议服务器和客户端。它极大简化了网络编程,如TCP和UDP套接字服务器。 

3、Grizzly 

Grizzly是一种应用程序框架,专门解决编写成千上万用户访问服务器时候产生的各种问题。使用JAVANIO作为基础,并隐藏其编程的复杂性。容易使用的高性能的API。带来非阻塞socketd到协议处理层。利用高性能的缓冲和缓冲管理使用高性能的线程池。 


其他常见问题

1、IO流的概念?

IO流用来处理设备之间的数据传输,Java对数据的操作是通过流的方式,Java用于操作流的类都在IO包中。

2、Java中有几种类型的流?

1)按照操作单元划分:字节流字符流。字节流继承inputStream和OutputStream,字符流继承自InputSteamReader和OutputStreamWriter。

2)按照流的流向划分:输入流输出流。

3)按照实现功能划分:节点流处理流。节点流,可以从或向一个特定的地方(节点)读写数据。如 FileReader;处理流是对一个 已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写,如 BufferedReader。

Java中IO流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, 40多个类都是从如下4个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

3、字符流和字节流有什么区别?

字节流读取的时候,读到一个字节就返回一个字节; 字符流使用了字节流读到一个或多个字节(中文对应的字节 数是两个,在 UTF-8 码表中是 3 个字节)时。先去查指定的编码表,将查到的字符返回。 字节流可以处理所有类型数据,如:图片,MP3,AVI 视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。字节流主要是操作 byte 类型数据,以 byte 数组为准,主要操作类就是OutputStream、InputStream。

字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字 节,操作字节和字节数组。所以字符流是由 Java 虚拟机将字节转化为 2 个字节的 Unicode 字符为单位的字符而成的, 所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用 字符流好点。在程序中一个字符等于两个字节,java 提供了 Reader、Writer 两个专门操作字符流的类。

字节流用于操作包含ASCII字符的文件。JAVA也支持其他的字符如Unicode,为了读取包含Unicode字符的文件,JAVA语言引入了字符流。ASCII作为Unicode的子集,对于英语字符的文件,可以使用字节流也可以使用字符流。

4、如何将字节流如何转为字符流?

字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。 字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。

5、字节缓冲输入流和字节缓冲输出流?

字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多这是加入了数组这样的缓冲区效果。java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式),所以提供了字节缓冲区流。

  • 字节缓冲输出流:BufferedOutputStream
  • 字节缓冲输入流:BufferedInputStream  传递的是输入输出流对象而不是文件对象

字节缓冲区流仅仅提供缓冲区,为高效而设计的。但是,真正的读写操作还得靠基本的流对象实现。

6、如何实现对象的克隆?

  • 实现 Cloneable 接口并重写 Object 类中的 clone()方法。
  • 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

7、什么是 java 序列化,如何实现 java 序列化?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读 写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

序列化的实现:将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法, implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就 可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。

8、close( )和flush( )的区别?

  • close():关闭流对象,但是先刷新一次缓冲区,关闭之后,流对象不可以继续再使用了。
  • flush():仅仅刷新缓冲区,刷新之后,流对象还可以继续使用。

9、read()方法返回值为什么是int,而不是byte?

因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到111111111,那么这11111111是byte类型的-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收,如果11111111会在其前面补上24个0凑足4个字节,那么byte类型的-1就变成int类型的255了这样可以保证整个数据读完,而结束标记的-1就是int类型。

10、Java IO体系中有太多的对象,到底用哪个呢? 

明确操作的数据设备。 

  • 数据source对应的设备:硬盘(File),内存(数组),键盘(System.in) 
  • 数据destination对应的设备:硬盘(File),内存(数组),控制台(System.out)。

记住,只要一读取键盘录入,就用这句话。 

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); 
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

11、Java IO 中的设计模式?(重点)

重点是用到两个设计模式:装饰模式和适配器模式

  • 装饰模式:在由InputStream、OutputStream、Reader和Writer代表的等级结构内部,有一些流处理器可以对另一些流处理器起到装饰作用,形成新的、具有改善了的功能的流处理器。
  • 适配器模式:在由InputStream、OutputStream、Reader和Writer代表的等级结构内部,有一些流处理器是对其他类型的流处理器的适配,这就是适配器的应用。

12、在文件拷贝的时候,哪一种流可用于提升更多的性能?

  • 在字节流的时候,使用BufferedInputStream和BufferedOutputStream。
  • 在字符流的时候,使用BufferedReader和BufferedWriter。

13、说说管道流(Piped Stream)

  • 有四种管道流:PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
  • 在多个线程或进程中传递数据的时候管道流非常有用。

14、说说File类

  • 它不属于IO流,也不是用于文件操作的。
  • 它主要是用于获取一个文件的属性、读写权限、大小等信息。

15、说说RandomAccessFile?

  • 它在java.io包中是一个特殊的类,既不是输入流也不是输出流,它两者都可以做到。
  • 它是Object的直接子类。通常来说,一个流只有一个功能,要么读,要么写。但是RandomAccessFile类既可以读文件,也可以写文件。
  • DataInputStream和DataOutputStream有的方法,在RandomAccessFile中都存在。

16、什么是Filter流?

Filter Stream是一种IO流。Filter流的主要作用是:对存在的流增加一些额外的功能,像给目标文件增加源文件中不存在的行数,或者增加拷贝的性能。在java.io包中主要由4个可用的filter Stream组成。两个字节filter stream,两个字符filter stream。分别是:FilterInputStreamFilterOutputStreamFilterReaderFilterWriter

17、FileInputStream和FileOutputStream是什么?
这是在拷贝文件操作的时候,经常用到的两个类。在处理小文件的时候,它们的性能表现还不错,在大文件的时候,最好使用BufferedInputStream(或BufferedReader)和BufferedOutputStream(或BufferedWriter)。

18、说说PrintStream和PrintWriter的异同点?

他们两个的功能相同,但是属于不同的分类:字节流和字符流。他们都有println()方法。

19、Java IO运行机制图解(重点)

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值