学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为非阻塞模式:
serverSocketChannel.configureBlocking(false);
NIO中可以在Selector中注册感兴趣的IO事件,比如:
这4个事件可以用SelectionKey的4个常量来表示:
注册相应的感兴趣的事件之后,调用Selector.select()方法会返回就绪的事件数,然后通过selector.selectedKeys()返回就绪的SelectKey集合,再调用相应的方法读写数据就可以了。这样做的好处是可以在一个线程中注册多个IO事件,对于读写事件不是很频繁的情况可以提高CPU的利用率。
看到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可以理解为一个连续的基本数据类型的数组,Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须经过Buffer。每种基本数据类型都有对应的Buffer,比如ByteBuffer,CharBuffer。
ByteBuffer有两种模式:直接/间接模式,间接模式是操作堆内存 (byte[]),但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存,这时就必须使用"直接"模式,即MappedByteBuffer内存文件映射,MappedByteBuffer将文件映射到内存中(虚拟内存),文件较大时可以分段映射。
内存映射文件和普通IO相比的优点:
使用举例:
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
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8