Java NIO是自jdk1.4以来就有的一个包。里面提供了大量的与普通IO不同的API。主要的不同点就在于N,有两种解释,一种是new,一种是nonblocking。学习完了之后我觉得两种说法都对,因为这部分API和典型的IO思想上完全不一样,而不一样之处中很重要的一点就是nonblocking这个特性。
整体的学习是从构建非阻塞型Socket通信开始的,之前是一直在学习Socket的知识,因为在项目中用到了而且对于自己来说是难点,所以特地学习了一下,收获也非常多,不过Socket部分另起一篇文章写。
NIO之前就明白很重要,但是一开始看Java核心技术的介绍并没有看进去,以为很难,学完之后发现并不然,重要的是要找到一个好的资源。我对NIO的学习大部分都是通过一个外国小哥的博客,链接在这里(tutorials.jenkov.com)。文章是14年就发表在网站上的,感觉自己要跟上这个时代真的是要做很多努力。博客中技术十分全面,Java的各种核心包都有教程,虽是英文,但是并不难懂,且有图有代码结合说明十分的便于理解。
目前基本明白了NIO的思想以及各种API的调用,但是缺乏实战经验,所以理解可能很片面,还需要不断的加深理解。毕竟这个行业是要不断学习的嘛。
Buffer是什么呢?在我看来Buffer其实就是一个放数据的容器,换言之就是一个数组。可以向其中写数据,当然也可以向其中读数据。那么它内部是怎么控制的呢?
这其中涉及它的很重要的四个属性,mask、capacity、position、limit。其中mask就像它的字面意思,它用于用户做标记,通过合适的方法可以找到该标记然后对标记后的数据做处理。capacity是整个数组的长度,在创建Buffer实例时就已经确定了,是不变的。下面就是两个很重要的属性,关于怎么读写Buffer中的数据。position很好理解就是下一个要操作的数据位置,这个操作包括读写。而limit就是所能操作数据的界限。这四个属性有这样一种关系mask<=position<=limit<=capacity。
Buffer中有两个有意思的方法特别说一下,其中一个是flip(),另一个是compact()。flip()的实现代码非常简单如下所示:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
这是什么意思呢,其实非常简单,调用这个方法就是意味着你要从Buffer中往外读取已经存在于Buffer中的数据了。
与之相对的就是clear()与compat()方法就是整理出合适的空间可以向Buffer中写数据。
Channel
什么是Channel呢,与典型的IO中的Stream有些像,但是不一样,他也有很多种形式,比如处理文件的FileChannel和通信的SocketChannel等等。但是向其中读写数据的介质就是上面说到的Buffer。 数据的传送不像Stream一样是一个类似水管的形态,而更像矿车的轨道,Buffer就是矿车。
我自己对Channel的理解也不是很深刻,更深的理解只能等以后多实践才能得出吧。
Selector
说了这么半天,从一开头就说到nonblocking,但是还一直没介绍是啥呢。nonblocking就和Selector有关,Selector是一个nonblocking的Channel管理器,它的select方法告诉程序员哪些他们感兴趣的Channel可以用了。而兴趣可以在最开始注册的时候注册进去。
FileChannel不支持非阻塞型模式,所以以下所说的Channel都是SocketChannel。
这种方式的好处就是即使单线程也可以处理多个Channel,不像传统IO需要为每一个连接分配一个线程。在很多连接不是很活跃的情况下,极大的浪费了系统的资源。这里借鉴两个大佬的图。见下两图:
阻塞形式
而不阻塞的好处就在于没有数据可读写的时候可以做一些其他的事情,比如记录服务器状态(还没有想到更好的可干的事儿)。而select方法干的事儿和传统IO的阻塞读写形式有一个很好的比喻,就是说朋友和你约好了要来你家,但是不知道具体什么时候来,正常我们会怎么做呢,是不是定时去门口看看朋友有没有来,如果没来的话就回到客厅做点儿别的事儿,非阻塞型IO就是这么做的,传统IO的话就会一直站在家门口等到朋友来了才和朋友一起进门。由此可见当然是非阻塞型IO好咯。
但也并不是说阻塞型IO一无是处,阻塞型IO的服务器实现起来非常简单,数据处理起来也非常容易。在并发量不是很大的情况下,阻塞型IO实现的服务器就够用了。
因为线程是一个非常宝贵且费资源的资源,大佬的文章中写到要是有1,000,000个连接连接到服务器,且为每一个连接分配一个线程的话大概需要1TB的内存。所以说并发量很大的时候传统的阻塞型服务器是顶不住的,只有使用NIO,虽然编写NIO的服务器是很难的一件事,但是其突破了性能障碍。
这里引用一下大佬的原文:
NIO allows you to manage multiple channels (network connections or files) using only a single (or few) threads, but the cost is that parsing the data might be somewhat more complicated than when reading data from a blocking stream.
If you need to manage thousands of open connections simultanously, which each only send a little data, for instance a chat server, implementing the server in NIO is probably an advantage. Similarly, if you need to keep a lot of open connections to other computers, e.g. in a P2P network, using a single thread to manage all of your outbound connections might be an advantage.
大意就是当需要构建一个并发量大切每次传输数据少的情境下,例如聊天服务器,P2P网络等等,需要用到NIO来构建服务器通讯。