面试题-IO流
1. Java中有几种类型的流?
答案
(1)按照流的方向:输入流(inputStream)和输出流(outputStream)。
(2)按照实现功能分:节点流(可以从或向一个特定的地方(节点)读写数据。如FileReader)和处理流(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接)。
(3)按照处理数据的单位:字节流和字符流。字节流继承于InputStream和OutputStream,字符流继承于InputStreamReader和OutputStreamWriter。
2. 什么是Java序列化?
答案
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
3. 如何实现java序列化?
答案
序列化的实现,将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:File Output Stream)来构造一个Object Output Stream(对象流)对象,接着,使用Object Output Stream 对象的write Object(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
4. 字节流和字符流的区别?
答案
字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时。先去查指定的编码表,将查到的字符返回。
字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
字节流主要是操作 byte 类型数据,以 byte 数组为准,主要操作类就是 OutputStream、InputStream,字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。所以字符流是由 Java 虚拟机将字节转化为 2 个字节的 Unicode 字符为单位的字符而成的,所以它对多国语言支持性比较好!
如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点。在程序中一个字符等于两个字节,java 提供了 Reader、Writer 两个专门操作字符流的类。
5. PrintStream、BufferedWriter、PrintWriter的比较?
答案
- PrintStream类的输出功能非常强大,通常如果需要输出文本内容,都应该将输出流包装成PrintStream后进行输出。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream。
- BufferedWriter:将文本写入字符输出流,缓冲各个字符从而提供单个字符,数组和字符串的高效写入。通过write()方法可以将获取到的字符输出,然后通过newLine()进行换行操作。BufferedWriter中的字符流必须通过调用flush方法才能将其刷出去。并且BufferedWriter只能对字符流进行操作。如果要对字节流操作,则使用BufferedInputStream。
- PrintWriter的println方法自动添加换行,不会抛异常,若关心异常,需要调用checkError方法看是否有异常发生,PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush)。
6. 什么是节点流,什么是处理流,它们各有什么用处,处理流的创建有什么特征?
答案
- 节点流 直接与数据源相连,用于输入或者输出
- 处理流:在节点流的基础上对之进行加工,进行一些功能的扩展
- 处理流的构造器必须要传入节点流的子类
7. 什么是BIO?
答案
BIO就是传统的java.io包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。它的优点就是代码比较简单、直观;缺点就是IO的效率和扩展性很低,容易成为应用性能瓶颈。
8. 什么是NIO?
答案
是Java1.4引入的java.nio包,提供了Channel、Selector、Buffer等新的抽象,可以构建多路复用的、同步非阻塞IO程序,同时提供了更接近操作系统底层高性能的数据操作方式。
9. 什么AIO?
答案
AIO是Java1.7之后引入的包,是NIO的升级版本,提供了异步非阻塞的IO操作方式,所以人们叫它AIO(Asynchronous IO),异步IO是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
10. 同步与异步
答案
同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。而异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。我们可以用打电话和发短信来很好的比喻同步与异步操作。
11. 阻塞与非阻塞
答案
阻塞与非阻塞主要是从CPU的消耗上来说的,阻塞就是CPU停下来等待一个慢的操作完成CPU才接着完成其他的事。非阻塞就是在这个慢的操作在执行时CPU去干其他别的事,等这个慢的操作完成时,CPU再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高CPU的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的CPU使用时间能不能补偿系统的切换成本需要好好评估。
12. 同步、异步、阻塞、非阻塞
答案
-
同步阻塞
最常用的一种用法,使用也是最简单的,但是 I/O 性能一般很差,CPU 大部分在空闲状态。
-
同步非阻塞
提升 I/O 性能的常用手段,就是将 I/O 的阻塞改成非阻塞方式,尤其在网络 I/O 是长连接,同时传输数据也不是很多的情况下,提升性能非常有效。 这种方式通常能提升I/O 性能,但是会增加CPU 消耗,要考虑增加的 I/O 性能能不能补偿 CPU 的消耗,也就是系统的瓶颈是在 I/O 还是在 CPU 上。
-
异步阻塞
这种方式在分布式数据库中经常用到,例如在往一个分布式数据库中写一条记录,通常会有一份是同步阻塞的记录,而还有两至三份是备份记录会写到其它机器上,这些备份记录通常都是采用异步阻塞的方式写 I/O。异步阻塞对网络 I/O 能够提升效率,尤其像上面这种同时写多份相同数据的情况。
-
异步非阻塞
这种组合方式用起来比较复杂,只有在一些非常复杂的分布式情况下使用,像集群之间的消息同步机制一般用这种 I/O 组合方式。如 Cassandra 的 Gossip 通信机制就是采用异步非阻塞的方式。它适合同时要传多份相同的数据到集群中不同的机器,同时数据的传输量虽然不大,但是却非常频繁。这种网络 I/O 用这个方式性能能达到最高。
13. 通道是个什么意思?
答案
- 通道是对原I/O包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个Channel对象(通道)。一个Buffer实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中。同样地,从通道中读取的任何数据都要读到缓冲区中。Channel是一个对象,可以通过它读取和写入数据。拿NIO与原来的I/O做个比较,通道就像是流。
- 正如前面提到的,所有数据都通过Buffer对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
14. 缓冲区是什么意思?
答案
- Buffer是一个对象,它包含一些要写入或者刚读出的数据。在NIO中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中,您将数据直接写入或者将数据直接读到Stream对象中。
- 在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问NIO中的数据,您都是将它放到缓冲区中。
- 缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读 / 写进程。
15. IO多路复用的底层原理(!)
答案
IO多路复用使用两个系统调用(select / poll / epoll 和 recvfrom),blocking IO只调用了recvfrom;select / poll / epoll核心是可以同时处理多个connection,而不是更快,所以连接数不高的话,性能不一定比多线程 + 阻塞IO好,多路复用模型中,每一个socket,设置为non-blocking,阻塞是被select这个函数block,而不是被socket阻塞的。
(1)select机制
客户端操作服务器时就会产生这三种文件描述符(简称fd):writefds(写)、readfds(读)和exceptfds(异常)。select会阻塞住监视3类文件描述符,等有数据、可读、可写、出异常或超时就会返回;返回后通过遍历fdset整个数组来找到就绪的描述符fd,然后进行对应的IO操作。
优点:
几乎在所有的平台上支持,跨平台支持性好。
缺点:
由于是采用轮询方式全盘扫描,会随着文件描述符FD数量增多而性能下降。
每次调用select(),需要把fd集合从用户态拷贝到内核态,并进行遍历(消息传递都是从内核到用户空间)。
默认单个进程打开的FD有限制是1024个,可修改宏定义,但是效率仍然慢。
(2)poll机制
基本原理与select一致,只有没有最大文件描述符限制,因为采用的是链表存储fd。
(3)epoll机制
epoll之所以高性能是得益于它的三个函数
1. epoll_create()系统启动时,在Linux内核里面申请一个B+树结构文件系统,返回epoll对象,也是一个fd。
2. epoll_ctl()每新建一个连接,都通过该函数操作epoll对象,在这个对象里面修改添加删除对应的链接fd,绑定一个callback函数。
3. epoll_wait()轮询所有的callback集合,并完成对应的IO操作。
优点:
没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄。
效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降。
内核和用户空间mmap同一块内存实现。