Java NIO 基本概念

学Java的都会接触到IO,传统的IO是基于字节流和字符流的,数据只能单向传输,JDK1.5引入了NIO,主要包含三个核心概念,Selector,Buffer和Channel。NIO中数据都是通过缓冲区来操作,缓冲区中数据可以移动,可以通过buffer.flip()来改变读写模式,比如调用channel.read(buffer)从文件中写入数据到buffer,之后调用buffer.flip(),再调用Buffer.get()就可以读取buffer里的数据。

非阻塞

NIO另一个非常重要的特点就是支持非阻塞操作,传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,NIO中可以配置socket为非阻塞模式:

1
serverSocketChannel.configureBlocking(false);

NIO中可以在Selector中注册感兴趣的IO事件,比如:

  1. Connect
  2. Accept
  3. Read
  4. Write

这4个事件可以用SelectionKey的4个常量来表示:

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

注册相应的感兴趣的事件之后,调用Selector.select()方法会返回就绪的事件数,然后通过selector.selectedKeys()返回就绪的SelectKey集合,再调用相应的方法读写数据就可以了。这样做的好处是可以在一个线程中注册多个IO事件,对于读写事件不是很频繁的情况可以提高CPU的利用率。

关于Selector

看到Selector源码的应该知道,Selector的实现是基于IO多路复用机制,以Linux系统为例,传统的IO多路复用有select,poll和epoll,epoll由于其采用了事件驱动机制大大提高了IO多路复用的效率得到了广泛的应用,Linux平台下Java的Selector底层就是采用的Epoll,Mac OS采用的是Kqueue。

喜欢网络编程的朋友应该了解过MINA和Netty,是Java语言比较优秀的NIO开源框架,其采用了事件驱动机制(Reactor线程模型)、异步非阻塞,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。

关于Buffer

Buffer可以理解为一个连续的基本数据类型的数组,Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须经过Buffer。每种基本数据类型都有对应的Buffer,比如ByteBuffer,CharBuffer。

ByteBuffer有两种模式:直接/间接模式,间接模式是操作堆内存 (byte[]),但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存,这时就必须使用”直接”模式,即MappedByteBuffer内存文件映射,MappedByteBuffer将文件映射到内存中(虚拟内存),文件较大时可以分段映射。

内存映射文件

内存映射文件和普通IO相比的优点:

  1. 用户进程把文件看作是内存,不需要再使用read()和write()系统调用
  2. 数据自动从磁盘加载到内存,数据更新时自动写入到文件
  3. 操作系统的虚拟内存会智能缓存分页,根据内存使用情况自动管理内存
  4. 数据永远是分页对齐的

使用举例:

1
2
3
4
5
6
7
File file = new File(bigExcelFile);

//获取FileChannel
FileChannel fileChannel = new RandomAccessFile(file, "r").getChannel();

//使用channel.map()获取MappedByteBuffer
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());

更多关于MappedByteBuffer的信息可以参考mappedbytebuffer-tutorial

热评文章