一、I/O 基础入门
Java1.4之前的早期版本,对I/O的支持并不完善,开发人员在开发java高性能IO程序的时候,会面临一些巨大的挑战和困难,主要问题如下:
- 没有数据缓存区,IO性能存在问题
- 没有C或者C++的Channel概念,只有输入、输出流
- 同步阻塞式I/O通信(BIO),通常会让通讯线程长时间的阻塞服务端的响应
- 支持的字符集有限,硬件可移植性不好
在Java支持异步通讯之前的很长一段时间里,一直都是C/C++长期占据着高性能服务端程序开发的语言,Java的同步阻塞式I/O一直被人们锁所诟病。
1.1 Linux网络I/O模型简介
Linux的内核将所有外部设备都看作一个文件来操作, 对一个文件的读写操作会调用内核提供的提供命令,返回一个文件file descriptor (fd 文件描述符 ),而对一个socket的读写也会有相应的描述符,称为socketfd(socket 描述符),描述符就是一个数字,它指向内核的一个结构体(文件路径、数据区等一些属性)。
根据UNIX网络编程对I/O模型的分类,分成了5类I/O模型,如下:
- 阻塞IO模型:
- 非阻塞I/O模型:
- I/O复用模型
- 信号驱动I/O模型
- 异步I/O模型:
1.2 I/O多路复用技术
在I/O编程中,当需要同时处理多个客户端接入请求时,可以利用多个线程或者多路复用I/O技术进行处理,I/O多路复用通常把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程下可以同时处理多个客户端的I/O请求。与传统的多线程/多进程相比,I/O多路复用技术的最大优势是系统开销小,系统不需要创建额外的进程或者线程,也不需要维护这些线程或者进程的运行,降低了这些系统的维护工作量,节省了系统资源,I/O多路复用技术的应用尝尽如下:
- 服务器需要同时处理多个处于监听状态或者处于连接状态的socket
- 服务器需要同时处理多种网络协议的套接字
目前支持I/O多路复用的系统调用有select、pselect、poll、epoll,在Linux网络编程过程中,很长一段时间都是通过select进行轮询和网络事件通知,然而select的一些固有缺陷导致了它的应用收到了很大的限制,最终Linux不得不在新的内核版本中寻找select的替代方案,最终选择了epoll。epoll与select很多方面类似,为了克服select的缺陷,epoll在很多方面做了很多改进,现总结如下:
- 支持一个进程打开的socket 描述符(fd)不受限制(仅受限于操作系统的最大文件句柄数)
- I/O效率不会随着fd数目的增加而线性下降
- 使用mmap加速内核空间与用户空间的数据传递
所谓的零拷贝技术(利用了虚拟地址指向同一块物理地址) - epoll的API更加简单
二、Java的I/O演进
在JDK1.4推出Java NIO之前,基于jdk的所有I/O通讯模式都是阻塞等待模式,通常意义上的一请求一应答,性能和资源利用率存在着巨大的瓶颈。
正是由于JDK1.4拙劣的I/O通讯模式,使得JDK1.4提供了新的I/O类库支持非阻塞I/O
JDK1.0到JDK1.3,Java I/O的类库都非常原始,很多UNIX网络编程中的概念或者接口在I/O中都没有体现,例如 Pipe、Channel、ByteBuffer、Selector等等,终于在2002年发布JDK1.4时,新增了java.nio包,提供了很多异步I/O开发的API和类库,如下:
- 进行异步I/O操作的缓冲区ByteBuffer
- 进行异步I/O操作的管道Pipe和通道Channel,例如:SocketChannel、ServerSocketChannel
- 多种字符集的编码能力和解码能力
- 实现非阻塞I/O的多路复用选择器selector
新的NIO提供的类库,极大的促进了基于Java的非阻塞I/O编程的开发和应用,但是它也有不完善的地方,特别是对系统文件的处理仍显不足,主要问题如下: - 没有统一的文件属性(例如读写权限)
- API能力比较弱,例如对于路劲级联的操作和递归处理,往往需要自己实现
- 底层存储系统的高级API无法使用
2011年JDK1.7正式发布,它最大的亮点是堆NIO类库进行了升级。