IO基础

什么是流?作用是什么?

  • 流是一种有顺序的,有起点和终点的字节集合,是对数据传输的总成或抽象。即数据在两设备之间的传输称之为流,流的本质是数据传输,根据数据传输的特性讲流抽象为各种类,方便更直观的进行数据操作。

字符流和字节流的区别是区别是什么?

  • 字符流的由来:因为数据编码的不同,而有了对字符进行高效操作的流对象,其本质就是基于字节流读取时,去查了指定的码表。

  • 字符流和字节流的区别:

    • 读写单位不同:字节流一字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。

    • 处理对象不同:字节流能处理所有类型的数据,而字符流只能处理字符类型的数据。

    • 字节流操作的时候本身是不会用到缓冲区的,是对文件本身的直接操作。而字符流在操作的时候是会用到缓冲区的,通过缓冲区来操作文件。

  • 结论:优先使用字节流,首先因为在硬盘上所有的文件都是以字节的形式进行传输或保存的,包括图片等内容。但是字符流只是在内存中才会形成,所以在开发中字节流使用广泛。

读写原始数据,采用什么流?

  • InputStream/OutputStream

为了提高读写性能,采用什么流?

  • BufferedInputStream/BufferedOutputStream

对各种基本数据类型和String类型的读写,采用什么流?

  • DataInputStream/DataOutputStream

指定字符编码,采用什么流?

  • InputStreamReader/OutputStreamWriter

Linux常见IO模型有哪些?

  • linux下有五种常见的IO模型:

    1、阻塞 I/O(blocking IO)

    2、非阻塞 I/O(nonblocking IO)

    3、I/O 多路复用( IO multiplexing)

    4、信号驱动 I/O( signal driven IO)

    5、异步 I/O(asynchronous IO)

    只有⑤是异步模型,其余皆为同步模型

  • 阻塞IO模型: 阻塞IO模型是最常见的IO模型了,对于所有的“慢速设备”(socket、pipe、fifo、terminal)的IO默认的方式都是阻塞的方式。阻塞就是进程放弃cpu,让给其他进程使用cpu。进程阻塞最显著的表现就是进程睡眠了。阻塞的时间通常取决于数据是否到来。 这种方式使用简单,但随之而来的问题就是会形成阻塞,需要独立线程配合,而这些线程在大多数时候都是没有进行运算的。Java的BIO使用这种方式,问题带来的问题很明显,一个Socket需要一个独立的线程,因此,会造成线程膨胀

  • 非阻塞IO模型: 非阻塞IO就是设置IO相关的系统调用为non-blocking,随后进行的IO操作无论有没有可用数据都会立即返回,并设置errno为EWOULDBLOCK或者EAGAIN。我们可以通过主动check的方式(polling,轮询)确保IO有效时,随之进行相关的IO操作。当然这种方式看起来就似乎不太靠谱,浪费了太多的CPU时间

  • 多路复用IO模型: 为了解决阻塞I/O的问题,就有了I/O多路复用模型,多路复用就是用单独的线程(是内核级的, 可以认为是高效的优化的) 来统一等待所有的socket上的数据, 一当某个socket上有数据后, 就启用用户线程(可能是从线程池中取出, 而不是重新生成), copy socket data, 并且处理message.因为网络延迟的原因, 同时在处理socket data的用户线程往往比实际的socket数量要少很多. 所以实际应用中, 大部分是用线程池, 池中thread数量可随socket的高峰和低谷 而动态调整多路复用I/O中内核中统一的wait socket data那部分可以理解成是非阻塞, 也可以理解成阻塞. 可以理解成非阻塞 是因为它不是等到socket数据全部到达再处理, 而是有了一部分数据就会调用用户线程来处理, 理解成阻塞, 是因为它和用户空间(Appliction)层的非阻塞socket的不同是: socket中没有数据时, 内核还是wait(阻塞)的, 而用户空间的非阻塞socket没有数据也会返回, 会造成CPU的浪费Linux下的select和poll 就是多路复用模式,poll相对select,没有了句柄数的限制,但他们都是在内核层通过轮询socket句柄的方式来实现的, 没有利用更底层的notify机制. 但就算是这样,相对阻塞socket也已经进步了很多很多了! 毕竟用一个内核线程就解决了,阻塞socket中N多线程都在无谓地wait的局面多路复用I/O 还是让用户层来copy socket data. 这个过程是将内核中的socket buffer copy到用户空间的 buffer. 这有两个问题: 一是多了一次内核空间switch到用户空间的过程, 二是用户空间层不便暴露很低层但很高效的copy方式(比如DMA), 所以如果由内核层来做这个动作, 可以更好地提高效率

  • 信号驱动IO模型: 所谓信号驱动,就是利用信号机制,安装信号SIGIO的处理函数(进行IO相关操作),通过监控文件描述符,当其就绪时,通知目标进程进行IO操作(signal handler)

  • 异步IO模型: 由于异步IO请求只是写入了缓存,从缓存到硬盘是否成功不可知,因此异步IO相当于把一个IO拆成了两部分,一是发起请求,二是获取处理结果。因此,对应用来说增加了复杂性。但是异步IO的性能是所有很好的,而且异步的思想贯穿了IT系统方方面面

  • select、poll、epoll的区别是什么?

    • 支持一个进程所能打开的最大连接数
      • select 是 32位机默认是1024个,64位机默认是2048
      • poll 本质上于select 没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的
      • epoll 虽然有连接数上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的可以打开20万左右的连接。
    • fd剧增后带来的I/O效率问题
      • select 每次调用事都会对连接进行线性遍历,所以随着fd的增加会造成遍历速度慢,呈线性下降性能问题。
      • poll 同上
      • epoll epoll内核中实现是根据每个fd的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃的socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有的socket都很活跃的情况下,可能会有性能问题
    • 消息传递方式
      • select 内核需要将消息传递到用户空间,都需要内核拷贝动作
      • poll 同上
      • epoll 通过内核和用户空间共享一块内存来实现的

BIO、NIO、AIO 的区别是什么?**

含义方面:

  • BIO(Blocking IO)是同步并阻塞的 IO,线程发起 IO 请求后,不论内核是否准备好 IO 操作,都会一直阻塞直到操作完成;
  • NIO(Non-blocking IO)是同步非阻塞的 IO,线程发起 IO 请求后立即返回;内核在做好 IO 操作的准备之后,通过调用注册的回调函数通知线程做 IO 操作,线程开始阻塞,直到操作完成;
  • AIO(Asynchronous IO)是异步非阻塞的 IO,线程发起 IO 请求后立即返回;内存做好 IO 操作的准备之后,做 IO 操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做 IO 操作完成或者失败。

应用场景方面:

  • BIO 从 JDK1.4 之前的版本,适用于低负载、低并发、业务逻辑耗时较长的场景
  • NIO 从 JDK1.4 开始支持,适用于高负载高并发且业务逻辑简单(轻操作)的场景,典型场景是聊天服务器
  • AIO 从 JDK1.7 开始支持,适用于高负载高并发且业务逻辑复杂(重操作)的场景,典型场景是相册服务器

发展过程:

BIO->NIO->AIO

  • BIO:在服务端,通常是在 while 循环中调用 accept 方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成。 如果 BIO 要能够同时处理多个客户端请求,就必须使用多线程,即每次 accept 阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续 accept 等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理;

  • NIO :与 BIO 的最大的区别是多路复用的思想只需要开启一个线程就(或者少量多线程)可以处理来自多个客户端的 IO 事件,用来扩展 BIO 的高并发场景。

    • 若服务端监听到客户端连接请求,便为其建立通信套接字 (java 中就是通道),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通信套接字。
    • 若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。
  • AIO:与 NIO 不同,当进行读写操作时,只须直接调用 API 的 read 或 write 方法即可。这两种方法均为异步的,完成后会主动调用回调函数。如果是读操作,操作系统会将可读的流传入 read 方法的缓冲区,并通知应用程序;如果是写操作,操作系统在将 write 方法传递的流写入完成后,也会通知应用程序。 在 JDK1.7 中,主要在 java.nio.channels 包下增加了下面四个异步通道来实现:

    • AsynchronousSocketChannel

    • AsynchronousServerSocketChannel

    • AsynchronousFileChannel

    • AsynchronousDatagramChannel

      这四个类的 read/write 方法,都会返回一个带回调函数的对象,当执行完读取 / 写入操作后,直接调用回调函数。

字节流、字符流的区别及适用场景分别是什么?

区别:

  • 处理单元不同,字节流处理的最基本单位为 1 个字节,字符流处理的最基本的单元是 Unicode 代码单元(大小 为2 字节)
  • 字节流默认不使用缓冲区;字符流使用缓冲区

使用场景:

  • 字节流实际上可以处理任何文件,因为字节是存储的基础单元;而待处理的流如果是可打印的字符,那么用字符流更方便一些

JDK 中, 字节流操作一般都是 InputStream, OutputStream 以及各种包装类;而字符流字符操作一般使用 Writer,Reader 等

JavaIO基础总结


文章推荐:

Java I/O的工作机制:深入分析Java I/O的工作机制

相关文章: