Java IO

192 阅读12分钟

1、IO流分为几种?

  • Reader字符读取
  • Writer字符写出
  • InputStream字符读取
  • OuputStream字符写出

2、Java中的IO是什么?

可以从这几个方面来回答:输入输出的概念IO的分类(介绍IO分类,包括字节流和字符流,以及它们的特点和适用场景。同时,可以提到Java中的IO操作还包括文件IO、网络IO、内存IO等)、IO特性IO异常处理IO性能优化(介绍Java中IO操作的性能优化,例如使用缓冲区、使用NIO、使用线程池等方法)、IO应用场景

总之,在回答Java中的IO是什么时,需要全面、准确地介绍Java中IO的相关知识点,以展示自己的专业能力和知识水平。

参考答案

IO(Input Output)用于实现对数据的输入与输出操作,Java把不同的输入/输出源(键盘、文件、网络等)抽象表述为流(Stream)。流是从起源到接收的有序数据,有了它程序就可以采用同一方式访问不同的输入/输出源。

  • 按照数据流向,可以将流分为输入流输出流,其中输入流只能读取数据、不能写入数据,而输出流只能写入数据、不能读取数据。
  • 按照数据类型,可以将流分为字节流字符流,其中字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。
  • 按照处理功能,可以将流分为节点流处理流,其中节点流可以直接从/向一个特定的IO设备(磁盘、网络等)读/写数据,也称为低级流,而处理流是对节点流的连接或封装,用于简化数据读/写功能或提高效率,也称为高级流。

Java提供了大量的类来支持IO操作,下表给大家整理了其中比较常用的一些类。其中,黑色字体的是抽象基类,其他所有的类都继承自它们。红色字体的是节点流,蓝色字体的是处理流。

image.png

根据命名很容易理解各个流的作用:

  • 以File开头的文件流用于访问文件;
  • 以ByteArray/CharArray开头的流用于访问内存中的数组;
  • 以Piped开头的管道流用于访问管道,实现进程之间的通信;
  • 以String开头的流用于访问内存中的字符串;
  • 以Buffered开头的缓冲流,用于在读写数据时对数据进行缓存,以减少IO次数;
  • InputStreamReader、InputStreamWriter是转换流,用于将字节流转换为字符流;
  • 以Object开头的流是对象流,用于实现对象的序列化;
  • 以Print开头的流是打印流,用于简化打印操作;
  • 以Pushback开头的流是推回输入流,用于将已读入的数据推回到缓冲区,从而实现再次读取;
  • 以Data开头的流是特殊流,用于读写Java基本类型的数据。

3、字节流与字符流的区别?

  1. 字节流以字节为单位进行输入输出,而字符流以字符为单位进行输入输出。

  2. 字节流适合所有类型文件的数据传输,因为计算机字节(Byte)是电脑中表示信息含义的最小单位。字符流只能够处理纯文本数据,其他类型数据不行,但是字符流处理文本要比字节流处理文本要方便

  3. 字节流对于处理二进制数据比字符流更加高效,而字符流对于处理文本数据比字节流更加高效(字节流通常用于处理图像、音频等二进制数据,而字符流通常用于处理文本文件)。

  4. 字节流的基类是InputStream和OutputStream,而字符流的基类是Reader和Writer。

总之,字节流和字符流各有优缺点,选择使用哪种流取决于处理的数据类型和具体需求。

4、I/O的阻塞发生在哪里

I/O的阻塞发生在等待数据传输的过程中。当程序请求I/O操作时,它会等待数据从输入设备传输到内存中或从内存传输到输出设备中。如果数据传输速度较慢,程序可能会被阻塞,直到数据传输完成。此时,CPU无法执行其他任务,因为它需要等待I/O操作完成。

5、怎么用流打开一个大文件?

参考答案

打开大文件,应避免直接将文件中的数据全部读取到内存中,可以采用分次读取的方式。

  1. 使用缓冲流。缓冲流内部维护了一个缓冲区,通过与缓冲区的交互,减少与设备的交互次数。使用缓冲输入流时,它每次会读取一批数据将缓冲区填满,每次调用读取方法并不是直接从设备取值,而是从缓冲区取值,当缓冲区为空时,它会再一次读取数据,将缓冲区填满。使用缓冲输出流时,每次调用写入方法并不是直接写入到设备,而是写入缓冲区,当缓冲区填满时它会自动刷入设备。
  2. 使用NIO。NIO采用内存映射文件的方式来处理输入/输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念),通过这种方式来进行输入/输出比传统的输入/输出要快得多。

6、Java中NIO是什么?

面试时可以从NIO的概述核心组件与IO的区别应用场景等方面进行回答。

答案:Java NIO(New IO)是Java1.4 中引入的新IO API,它提供了一种不同于Java IO(InputStream 和 OutputStream)的IO处理方式(这种处理方式是基于缓冲区和通道的IO处理方式)。相对于Java IO,NIO提供了更快、更高效、更灵活IO操作方式

Java NIO主要由以下三个部分组成:

  1. 缓冲区(Buffer):NIO中的缓冲区是一个连续的内存区域,用于存储数据。NIO通过缓冲区来实现数据的读写操作,缓冲区可以读写数据,并可以反转以实现读写转换。

  2. 通道(Channel):NIO中的通道是一个双向的数据传输通道,可以通过通道进行数据的读写操作。NIO中的通道类似于Java IO中的流,但是通道可以同时进行读写操作,而流只能进行单向的读或写操作。

  3. 选择器(Selector):NIO中的选择器是一个多路复用器,它可以同时监控多个通道的IO事件,当某个通道有IO事件发生时,选择器会通知该通道进行处理。

Java NIO相对于Java IO的优点在于:

  1. 更快的速度:NIO的缓冲区和通道可以直接操作内存,避免了Java IO中的数据复制操作,因此速度更快。

  2. 更高效的内存管理:NIO中的缓冲区可以使用直接内存,避免了Java IO中的堆内存和操作系统内存之间的数据拷贝,因此内存管理更高效。

  3. 更灵活的IO操作:NIO中的通道可以同时进行读写操作,而Java IO中的流只能进行单向的读或写操作,因此NIO更灵活。

总之,Java NIO提供了一种更快、更高效、更灵活的IO处理方式,适用于需要高性能IO操作的场景。

另可参考:请你讲一下Java NIO

7、IO和NIO的区别

IO和NIO都是Java中常见的输入输出方式,二者的区别主要有以下几个方面:

  1. 阻塞和非阻塞:IO是阻塞式的,当线程调用IO进行读写操作时,线程会一直等待,直到操作完成。而NIO是非阻塞式的,当线程调用NIO进行读写操作时,线程不会等待,而是继续执行其他任务,直到操作完成时才返回结果。

  2. 缓冲区:IO是面向流的,数据直接从输入流或输出流中读取或写入,没有缓冲区。而NIO是面向缓冲区的,数据先被读取到缓冲区中,再从缓冲区中读取或写入。

  3. 选择器:NIO提供了选择器(Selector)的支持,可以同时监控多个通道的数据状态,当有数据准备就绪时,选择器会通知程序进行处理。而IO没有选择器的概念。

  4. 线程模型:IO通常使用阻塞式的I/O模型,每个连接对应一个线程,当有大量的连接时,会导致线程数量过多,占用过多的系统资源。而NIO采用了多路复用的技术,使用少量的线程处理多个连接,可以大大减少线程数量,提高系统的并发性能。

  5. 数据处理方式:IO是字节流和字符流的处理方式,而NIO是基于通道(Channel)和缓存区(Buffer)的处理方式,基于Channel和Buffer的处理方式更加灵活,可以进行更加细粒度的控制。

总之,IO和NIO都有各自的优缺点,具体使用哪种方式要根据具体的业务场景和需求来选择。

8、通过故事讲清NIO

假设某银行只有10个职员。该银行的业务流程分为以下4个步骤:

  1. 顾客填申请表(5分钟);
  2. 职员审核(1分钟);
  3. 职员叫保安去金库取钱(3分钟);
  4. 职员打印票据,并将钱和票据返回给顾客(1分钟)。

下面我们看看银行不同的工作方式对其工作效率到底有何影响。

首先是BIO方式。

每来一个顾客,马上由一位职员来接待处理,并且这个职员需要负责以上4个完整流程。当超过10个顾客时,剩余的顾客需要排队等候。

一个职员处理一个顾客需要10分钟(5+1+3+1)时间。一个小时(60分钟)能处理6个顾客,一共10个职员,那就是只能处理60个顾客。

可以看到银行职员的工作状态并不饱和,比如在第1步,其实是处于等待中。

这种工作其实就是BIO每次来一个请求(顾客),就分配到线程池中由一个线程(职员)处理,如果超出了线程池的最大上限(10个),就扔到队列等待。

那么如何提高银行的吞吐量呢?

思路就是:分而治之,将任务拆分开来,由专门的人负责专门的任务。

具体来讲,银行专门指派一名职员A,A的工作就是每当有顾客到银行,他就递上表格让顾客填写。每当有顾客填好表后,A就将其随机指派给剩余的9名职员完成后续步骤。

这种方式下,假设顾客非常多,职员A的工作处于饱和中,他不断的将填好表的顾客带到柜台处理。

柜台一个职员5分钟能处理完一个顾客,一个小时9名职员能处理:9*(60/5)=108。

可见工作方式的转变能带来效率的极大提升。这种工作方式其实就NIO的思路。

下图是非常经典的NIO说明图,mainReactor线程负责监听server socket,接收新连接,并将建立的socket分派给subReactor

subReactor可以是一个线程,也可以是线程池,负责多路分离已连接的socket,读写网络数据。这里的读写网络数据可类比顾客填表这一耗时动作,对具体的业务处理功能,其扔给worker线程池完成

可以看到典型NIO有三类线程,分别是mainReactor线程、subReactor线程、work线程。

不同的线程干专业的事情,最终每个线程都没空着,系统的吞吐量自然就上去了。

image.png

那这个流程还有没有什么可以提高的地方呢?

可以看到,在这个业务流程里边第3个步骤,职员叫保安去金库取钱(3分钟)。这3分钟柜台职员是在等待中度过的,可以把这3分钟利用起来。

还是分而治之的思路,指派1个职员B来专门负责第3步骤。

每当柜台员工完成第2步时,就通知职员B来负责与保安沟通取钱。这时候柜台员工可以继续处理下一个顾客。

当职员B拿到钱之后,通知顾客钱已经到柜台了,让顾客重新排队处理,当柜台职员再次服务该顾客时,发现该顾客前3步已经完成,直接执行第4步即可。

在当今web服务中,经常需要通过RPC或者Http等方式调用第三方服务,这里对应的就是第3步,如果这步耗时较长,通过异步方式将能极大降低资源使用率。

NIO+异步的方式能让少量的线程做大量的事情。这适用于很多应用场景,比如代理服务、api服务、长连接服务等等。这些应用如果用同步方式将耗费大量机器资源。

不过虽然NIO+异步能提高系统吞吐量,但其并不能让一个请求的等待时间下降,相反可能会增加等待时间。

最后,NIO基本思想总结起来就是:分而治之,将任务拆分开来,由专门的人负责专门的任务