Nio学习
nio学习
文章是自己学习后的一个总结,如果有什么理解不对的地方,欢迎留言
这一章你只需要明白什么是nio,nio中有什么,nio能做什么即可。
更为详细的解释,可以去看java nio这本书,当然博主也在慢慢学习,也会在别的随笔中写出更为详细的解释!加油啊小伙伴!
什么是nio?
java.nio全称java non-blocking io(实际上是 new io),是指jdk1.4 及以上版本里提供的新api(new io) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。
什么是阻塞式什么是非阻塞式?
- 传统io: 传统的io是阻塞式的,当服务器要从一个文件系统读取数据的时候,需要建立一个线程去读取,但是刚开始读取的时候,有可能文件系统并没有把数据准备好,但是该线程只能等待文件系统把数据准备好再进行读的操作,无法先去做别的事情。这就是阻塞
- nio : nio可以解决传统io中的堵塞问题,使用了selector (选择器,稍后会详解)监听,数据有没有准备好,当数据准备好,服务器在为该读操作分配线程。这样一来,服务器可以很好的利用仅有的线程资源。
就好比生活中在某滴上预约了车,估摸这8:00司机会到达,你7:50就在小区门口等待,那么你将有10分钟的等待时间,这就是阻塞。相反,你等8:00司机到了并且给你打电话通知你已经到小区门口了,这个时候你再出门。这样你是不是就节约了10分钟,而这10分钟内,你可以选择干点有人生意义的事情,这样就是非阻塞。
但是请注意一点,并不是用了nio就不会发生堵塞!!!并不是用了nio就不会发生堵塞!!!并不是用了nio就不会发生堵塞!!!重要的事情说三遍。在nio中也分阻塞和非阻塞,后面会说。
你应该了解的nio中三个组件
nio中有三个重要概念,(⊙o⊙)…怎么说捏,如果没记住,那你恐怕没有办法继续向下看。
- buffer: 缓冲区,用来存储数据的容器。实际上是个数组
- channel: 通道, 表示io源和应用程序之间的连接
- selector: 选择器,如果channel注册进选择器中,那么selector就可以监听channel。一个selector可以监听多个channel.
buffer可以理解成火车,channel可以理解为铁路,buffer在channel中行驶。因此,我能得出一个结论,channel并不存储数据!!!
channel每次都从buffer中读数据,也把数据写入到buffer中
buffer学习(java.nio.buffer)
buffer实际上是一个数组,buffer可以有多种类型, java中的基本类型都可以和buffer关联(boolean除外).
突然间觉得boolean好可怜,人家不带它玩,用的最多的可能就是bytebuffer了。
在buffer中还有三个重要概念:容量、限制和位置.
- position:标记当前操作数所在位置
- limit:表示缓冲区中可以操作数据的大小,limit后的数据不能读写
- capacity: 标记当前容量大小
比如我初始化一个
bytebuffer buffer = new bytebuffer.allocate(5);
(allocate可以指定buffer缓冲区的大小。)那么position,limit,capacith的关系如下
画图太难了,臣妾做不到啊!!!这个时候我
string str = "123"; buffer.put(str.getbytes());
那么position就会移动到第三格,limit,capacity还是不会变。
但是如果如果把buffer切换成读模式
buffer.flip()
那么当前position: 3 , limit: 3, capacity: 5
@test public void test() { string str = "123"; //指定buffer的容量大小 bytebuffer buffer = bytebuffer.allocate(1024); //属性 system.out.println("-----------------------"); //当前操作的数据所在的位置 system.out.println(buffer.position()); //界限,表示缓冲区中可以操作数据的大小, limit后的数据不能读写 system.out.println(buffer.limit()); //缓冲区中最大存储数据容量 system.out.println(buffer.capacity()); buffer.put(str.getbytes()); system.out.println("---------put--------------"); system.out.println(buffer.position()); system.out.println(buffer.limit()); system.out.println(buffer.capacity()); //切换成读模式 buffer.flip(); system.out.println("---------flip--------------"); system.out.println(buffer.position()); system.out.println(buffer.limit()); system.out.println(buffer.capacity()); bytearrs = new byte[buffer.limit()]; bytebuffer bytebuffer = buffer.get(bytearrs); system.out.println(bytebuffer.tostring()); // rewind,切换成读模式,可以重新读 buffer.rewind(); system.out.println("===========rewind============"); system.out.println(buffer.tostring()); //清空缓存区 buffer.clear(); }
channel(java.nio.channels.channel)
channel中主要的实现类:filechannel,socketchannel,serversocketchannel。
获取channel的方法:
jdk1.7以前: 通过io流获得到channel
/** * 利用通道完成文件复制 * @throws ioexception */ @test public void test() { long start = system.currenttimemillis(); fileinputstream fis = null; fileoutputstream fos = null; filechannel inchannel = null; filechannel outchannel = null; try { //jdk1.7以前nio 的获取通道的写法 fis = new fileinputstream("./resource/java nio.pdf"); fos = new fileoutputstream("./resource/democopytest.jpeg"); //1.获取通道 inchannel = fis.getchannel(); outchannel = fos.getchannel(); //2.创建缓冲区,并分配大小 bytebuffer bytebuffer = bytebuffer.allocate(1024); //3.把数据写进缓冲区 while (inchannel.read(bytebuffer) != -1) { //4.切换读取数据模式 bytebuffer.flip(); outchannel.write(bytebuffer); bytebuffer.clear(); } } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } finally { try { if (outchannel != null) { outchannel.close(); } if (inchannel != null) { inchannel.close(); } if (fos != null) { fos.close(); } if (fis != null) { fis.close(); } } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } } long end = system.currenttimemillis(); system.out.println("耗费时间非直接缓存" + (end - start)); }
jdk1.7以后:可以直接通过open方法获得到channel
@test public void test2() { long start = system.currenttimemillis(); filechannel inchannel = null; filechannel outchannel = null; try { //建立通道 inchannel = filechannel.open(paths.get("./resource/java nio.pdf"), standardopenoption.read); outchannel = filechannel.open(paths.get("./resource/java niocopytest2.pdf"), standardopenoption.read, standardopenoption.write, standardopenoption.create); // inchannel.transferto(0, inchannel.size(), outchannel); outchannel.transferfrom(inchannel, 0, inchannel.size()); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } finally { try { if (outchannel != null) { outchannel.close(); } if (inchannel != null) { inchannel.close(); } } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } } long end = system.currenttimemillis(); system.out.println("耗费时间直接缓冲区" + (end - start)); }
/** * 直接缓冲区,用内存映射文件完成 * 可能遇到的问题: 文件已经copy完成,但是程序可能没有完成。我们只能控制什么时候写入映射文件,但是不能控制什么时候从映射文件写入磁盘 */ @test public void test1() { long start = system.currenttimemillis(); filechannel inchannel = null; filechannel outchannel = null; mappedbytebuffer inmap = null; mappedbytebuffer outmap = null; try { //建立通道 inchannel = filechannel.open(paths.get("./resource/java nio.pdf"), standardopenoption.read); outchannel = filechannel.open(paths.get("./resource/java niocopytest2.pdf"), standardopenoption.read, standardopenoption.write, standardopenoption.create); //因为是内存文件映射,我们不需要读流,内存映射文件 //mappedbytebuffer 相当于allocatedriect() inmap = inchannel.map(mapmode.read_only, 0, inchannel.size()); outmap = outchannel.map(mapmode.read_write, 0, inchannel.size()); byte[] bytes = new byte[inmap.limit()]; inmap.get(bytes); outmap.put(bytes); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } finally { try { if (outchannel != null) { outchannel.close(); } if (inchannel != null) { inchannel.close(); } } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } } long end = system.currenttimemillis(); system.out.println("耗费时间直接缓冲区" + (end - start)); }
selector(java.nio.channels.selector )
使用selector可以实现非阻塞,创建selector
selector selector = selector.open();
阻塞式nio和非阻塞式nio
nio是如何实现非阻塞式io的?
嗯。。这个问题我们还是得看一张图。阻塞式是这样的,客户端直接和服务器端建立连接,不需要中间监听器
没用selector所以还是阻塞式nio
@test public void servertest() { serversocketchannel serversocketchannel = null; filechannel filechannel = null; socketchannel socketchannel = null; try { serversocketchannel = serversocketchannel.open(); serversocketchannel.bind(new inetsocketaddress(9898)); socketchannel = serversocketchannel.accept(); bytebuffer bytebuffer = bytebuffer.allocate(1024); filechannel = filechannel.open(paths.get("./resource/blocktest.jpeg"), standardopenoption.write, standardopenoption.create); while (socketchannel.read(bytebuffer) != -1) { bytebuffer.flip(); system.out.println(bytebuffer); filechannel.write(bytebuffer); bytebuffer.clear(); } } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } finally { try { if (filechannel != null) { filechannel.close(); } if (serversocketchannel != null) { serversocketchannel.close(); } if (socketchannel != null) { socketchannel.close(); } } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } } }
非阻塞式是这样的
当客户端发请求时会先到达selector,selector就像一堵墙一样堵在了客户端和服务器段。发请求的同时把channel注册到selector中。selector监听channel的状态
渠道分为4种状态:
public static final int op_read = 1 << 0; public static final int op_write = 1 << 2; public static final int op_connect = 1 << 3;
public static final int op_accept = 1 << 4;
拿socketchannel来举例子,服务器端
@test public void server() { serversocketchannel serversocketchannel = null; try { serversocketchannel = serversocketchannel.open(); serversocketchannel.configureblocking(false); //创建selector对象 selector selector = selector.open(); //把serversocketchannel交给selector管理,并绑定监听状态op_accept serversocketchannel.register(selector, selectionkey.op_accept); serversocketchannel.bind(new inetsocketaddress(9898)); while (selector.select() > 0) { //获得迭代器 iterator<selectionkey> iterator = selector.selectedkeys().iterator(); while (iterator.hasnext()) { selectionkey selectionkey = iterator.next(); //判断channel是否是 is ready to accept准备 if (selectionkey.isacceptable()) { //服务器连接请求!!!这个时候才连接,而不是像阻塞io那样,不管三七二十一直接连接请求 socketchannel socketchannel = serversocketchannel.accept(); //设置成非阻塞 socketchannel.configureblocking(false); //注册channel到selector,这里注意,socketchannel是一个新的渠道也需要注册 //监听read socketchannel.register(selector, selectionkey.op_read); } else if (selectionkey.isreadable()){ //获取当前选择器中读就绪的channel socketchannel socketchannel = (socketchannel)selectionkey.channel(); bytebuffer buffer = bytebuffer.allocate(1024); int len = 0; while ((len = socketchannel.read(buffer) )!= -1) { buffer.flip(); //这里可以把客户端传过来的byte做一些转化 system.out.println(buffer); buffer.clear(); } } //从迭代器中把已经完成是事件移除 iterator.remove(); } } } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } }