java的几种IO

Java IO方式大体上可以分为三类,基于不同的io模型可以简单分为同步阻塞的BIO,同步非阻塞的NIO和异步非阻塞的AIO。IO又主要可以分为文件IO和网络IO。针对Java的网络IO模型,可以看网络IO模型(BIO,NIO,AIO)这篇博客。

先简单认识下这三种IO:
BIO:
首先,传统的java.io包是 blocking io(BIO),在jdk1.0的时候引入的,它提供了我们最熟知的一些IO功能,比如File抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说在读入输入流或者写入输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的好处就是代码比较简单、直观,缺点则是IO效率和扩展性存在局限性,容易成为应用性能的瓶颈。

一般常说的java中的io流指的就是java中BIO的具体实现

NIO:
NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel(通道)、Buffer(缓冲区)、Selector(选择器) 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。

AIO:
AIO 是 Java 1.7 之后引入的包,是对NIO进一步的改进,也被称为NIO2,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO)。异步IO操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。

同步和异步,阻塞和非阻塞

在具体了解这三种io之前,要先清楚一些概念:
区分同步或异步(synchronous/asynchronous):同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。(当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步)
而异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。(其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系。)

区分阻塞与非阻塞(blocking/non-blocking):阻塞与非阻塞主要是从 CPU 的消耗上来说的,阻塞就是 CPU 停下来线程挂起等待当前一个慢的操作完成 CPU 才接着完成其它的事。阻塞期间无法从事其他任务,只有当慢操作完成条件就绪才能继续。
非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事,等这个慢的操作完成时,CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。

阻塞和非阻塞通常是指客户端在发出请求后,在服务端处理这个请求的过程中,客户端本身是否直接挂起等待结果,还是继续做其他的任务。而异步和同步,则是对于请求结果的获取是客户端主动等待获取,还是由服务端来通知消息结果。

BIO

对于传统的java.io,BIO,我们都非常熟悉,总体上需要理解:

  1. BIO不仅仅是对文件的操作,网络编程中,比如Socket通信,都是典型的BIO操作目标。
  2. 输入流、输出流(InputStream/OutputStream)是用于读取或写入字节的,例如操作图片文件。
  3. 而Reader/Writer则是用于操作字符,增加了字符编解码等功能,适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字节,不管是网络通信还是文件读取,Reader/Writer相当于构建了应用逻辑和原始数据之间的桥梁。
  4. 处理纯文本数据时使用字符流(xxxReader/xxxWriter),处理非纯文本时使用字节流(xxxStream)。最后其实不管什么类型文件都可以用字节流处理,包括纯文本,但会增加一些额外的工作量。所以还是按原则选择最合适的流来处理
  5. BuferedOutputStream等带缓冲区的实现,可以避免频繁的磁盘读写,进而提高IO处理效率。这种设计利用了缓冲区,将批量数据进行一次操作,但在使用中千万别忘了结束时调用fush将未满的缓冲区数据进行写入。

面试题:介绍一下Java中的IO流
流是Java对不同输入源输出源的抽象,代表了从起源到接收的有序数据,有了它程序就可以采用统一的方式来访问不同的输入源和输出源了。
按照数据的流向,可以将流分为输入流和输出流。其中,输入流只能读取数据、不能写入数据,而输出流只能写入数据、不能读取数据。
按照数据的类型,可以将流分为字节流和字符流。其中,字节流操作的数据单元是byte(8位的字节),而字符流操作的数据单元是char(16位的字符)。
按照使用的场景,可以将流分为节点流和处理流。其中,节点流可以直接从/向一个特定的IO设备读/写数据,也称为低级流。而处理流则是对节点流的连接或封装,用于简化数据读/写功能或提高效率,也成为高级流。

NIO

NIO即New IO,这个库是在JDK1.4中才引入的。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO

NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

BIO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

NIO和BIO有相同的作用和目的,但实现方式不同,BIO主要用到的是流,而NIO主要用到的是块,所以NIO的效率要比IO高很多。

流与块的比较
NIO和BIO最大的区别是数据打包和传输方式。BIO是以流的方式处理数据,而NIO是以块的方式处理数据。
面向流的IO一次一个字节的处理数据,一个输入流产生一个字节,一个输出流就消费一个字节。为流式数据创建过滤器就变得非常容易,链接几个过滤器,以便对数据进行处理非常方便而简单,但是面向流的IO通常处理的很慢,此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。。
面向块的IO系统以块的形式处理数据。每一个操作都在一步中产生或消费一个数据块。按块要比按流快的多,但面向块的IO缺少了面向流IO所具有的有雅兴和简单性。

Channel:(通道),Channel是一个通道,可以通过它读取和写入数据。与流不同的是,流是单向的,而Channel是双向的。数据可以通过Channel读到Buffer里,也可以通过Channel写入到Buffer里。需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。为了支持不同的设备,Channel接口有好几种子类,如FileChannel用于访问磁盘文件、SocketChannel和ServerSocketChannel用于TCP协议的网络通信、DatagramChannel用于UDP协议的网络通信。

Buffer(缓冲区), NIO是面向缓冲区的,在NIO中所有的数据都是通过缓冲区处理的。Buffer就是缓冲区对象,无论读取还是写入,数据都是先进入Buffer的。Buffer的本质是一个数组,通常它是一个字节数组,也可以是其他类型的数组。Buffer是一个接口,它的实现类有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分别对应基本数据类型: byte, char, double, float, int, long, short。

Buffer对象包含三个重要的属性,分别是capacity、position、limit。其中,capacity代表Buffer的容量,就是说Buffer中最多只能写入capacity个数据。position代表访问的位置,它的初始值为0,每读取/写入一个数据,它就会向后移动一个位置。limit代表访问限制,就是本次操作最多能读取/写入多少个数据。这三个属性的关系是,position<=limit<=capacity,Buffer通过不断调整position和limit的值,使得自身可以不断复用。

Selector(选择器),Selector是多路复用器,可以通过它监听网络IO的状态。它可以不断轮询注册的Channel,如果某Channel上有连接、读取、写入事件发生,则这个Channel就处于就绪状态,就会被Selector轮询出来。所有被轮询出来的Channel集合,我们可以通过SelectionKey获取到,然后进行后续的IO操作。

总结一下NIO和BIO到底有什么区别?

  1. NIO是以块的方式处理数据,但是IO是以最基础的字节流的形式去写入和读出的。所以在效率上的话,肯定是NIO效率比IO效率会高出很多。
  2. NIO不在是和IO一样用OutputStream和InputStream 输入流的形式来进行处理数据的,但是又是基于这种流的形式,而是采用了通道和缓冲区的形式来进行处理数据的。
  3. NIO的通道是可以双向的,但是IO中的流只能是单向的。
  4. 还有就是NIO的缓冲区(其实也就是一个字节数组)还可以进行分片,可以建立只读缓冲区、直接缓冲区和间接缓冲区,只读缓冲区很明显就是字面意思,直接缓冲区是为加快 I/O 速度,而以一种特殊的方式分配其内存的缓冲区。
  5. NIO比传统的BIO核心区别就是,NIO采用的是多路复用的IO模型,普通的IO用的是阻塞的IO模型,两个之间的效率肯定是多路复用效率更高

AIO

jdk7中新增了一些与文件(网络)I/O相关的一些api。这些API被称为NIO.2,或称为AIO(Asynchronous I/O)。AIO最大的一个特性就是异步能力,这种能力对socket与文件I/O都起作用。AIO其实是一种在读写操作结束之前允许进行其他操作的I/O处理。AIO是对JDK1.4中提出的同步非阻塞I/O(NIO)的进一步增强。

jdk7主要增加了三个新的异步通道:

  • AsynchronousFileChannel: 用于文件异步读写;
  • AsynchronousSocketChannel: 客户端异步socket;
  • AsynchronousServerSocketChannel: 服务器异步socket。

因为AIO的实施需充分调用OS参与,IO需要操作系统支持、并发也同样需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。

Unix 的5种IO模型

Unix IO一共包含5种模型,分别是阻塞式IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO。Java的IO流(BIO)实现是依附于平台的,换句话说它依赖于上述5种模型的阻塞IO,Linux/Unix五种IO模型。实际上Java的IO流是基于阻塞式IO实现的。阻塞式IO是性能最差的IO模型了,所以Java的IO流并不是一个高效的实现,在Java的早期人们对Java性能的诟病也正源于此。

从1.4开始,Java提供了新的IO模型(NIO),这种IO模型是非阻塞IO,在Java中,NIO采用了Reactor模型实现多路复用。NIO学习–Reactor模型。(Reactor模式到底是什么?和NIO有什么关系?为什么Redis,Netty都用到了?
从1.7开始,Java又对IO模型进行了升级(NIO2),在本次升级中Java提供了异步IO模型(AIO),采用了Proactor模式,简化了程序编写。这两种IO模型的引入,使得Java处理IO的性能大大的提高了,我们在处理IO问题时也应该尽量选择NIO,而少用IO流。

参考:快速掌握java中的IO与NIO面试题

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值