java nio太复杂?军哥带你10分钟摸透它

大家好,这里是五彩石编程,我是军哥。java开发中,绕不开的一个技术点就是nio,和nio相关的概念也非常之多,经常一起出来的还有BIOAIOchannelepoll等,很容易就把人搞得头晕了,今天军哥就带大家梳理下思路,用10分钟的时间来摸透nio技术。


nio的故事

java早期只有io包,没有nio包,但io包在使用的过程中出现了很多问题,比如io是阻塞的,io操作也比较慢等等。后来,几位java开发者某天中午去了一家汉堡店吃汉堡,那时正是饭点,汉堡店的人非常多,3个点餐员前面,都排起了长队,他们每个人都点过汉堡后,就坐在靠近橱窗的位置,一边等着叫号,一边聊天,还好出餐的速度比较快,他们点的汉堡很快就做好了,吃完后他们还不想回去,就继续坐着,很快,其它客人就都吃完离开了,离开前都自觉的把餐盘送到了收餐具的窗口。最后只剩下了他们几个,这时候,其中一个开发者提了一个话题,说这家店的客人这么多,效率怎么还能这么高呀?其它人听完后,就开始分析,他们分析出来的要点主要有以下几个:

  1. 点餐员只负责点餐,客人多的时候就动态增加一个,直到最多3个点餐员
  2. 后厨负责查看单子,制作汉堡等各种耗时的操作,和点餐员的工作互不影响
  3. 后厨制作好一份食物后,就把食物放到取餐口,再通过叫号系统叫一下号,对应的客人听到后,就会自己来取自己点的食物,客人就不必一直等在取餐口了。

分析完了之后,一位开发者又提出了一个问题,如果我们也按这个步骤来改造io包,是不是也就解决了io阻塞的问题,还能提高io的效率呀!其他人听了之后,感觉有点意思,于是就快速回到了办公室,开始了改造工作。

首先客人来到汉堡店,一定是要完成一项吃东西的任务,而每个人的任务都不太一样,那就需要先把客人抽象出来,而每一个点餐员都会负责处理多个客人的任务,还可以动态增加点餐员,这个也得抽象出来,最好用线程池来实现动态增加的功能。餐做好了去通知客人,刚好可以用linux的epoll技术来完成,而做好的餐放在出餐口,等待客人来取,那么出餐口也比较重要,也得抽象出来。客人走的时候,都把餐盘送到收餐具的口,要处理这么多餐具,那么收餐具的口也比较重要,也得抽象出来,这样一来,所有需要的东西就都抽象出来了,接下来开始进行整理。

一个点餐员处理那么多的客人,好像和一台电视机可以处理多个频道的功能比较类似,那干脆把客人的抽象叫做channel频道吧,好直白的命名呀!他们貌似也意识到了这个问题,那么点餐员的这个抽象就绝对不能叫做tv,那就显得有点low了,既然点餐员可以选择为某个客户服务,也可以选择不为某个客户服务,那干脆叫selector选择器吧,貌似又有点直白了,但总比tv好吧,就这样定吧!出餐口经常会临时放几份做好的食物,不就是提供了一个缓冲的空间嘛,那干脆称其抽象为buffer吧,收餐具口貌似和出餐口功能类似,也会临时放几个餐具,那干脆把二者合二为一吧,大家都叫buffer,这下需要的抽象就都齐活了。

以上故事纯属虚构,只是为了帮助大家理解nio中核心的三大件:selectorchannelbuffer

nio的名字诞生

通过上面的故事,向大家描述了nio中三大件的由来,那么怎么实现这个思路呢?如果直接改造io包,那么对于已经大量使用io包的企业来说,升级的成本就太高了,既然这个思路和之前的io思路不一样,这个思路又这么新颖,干脆就叫new io吧,但new io的名字貌似又有点low,那么就用缩写这个常见技巧,立马就会显得高大上了,于是一个开创新的io时代的名字诞生了:nio

nio的设计及实现

有了nio的三大件抽象,就开始整起来吧。

selector的功能是平台相关的,linuxwindows在实现上各不相同。

channel用来代表一个具体的连接,具体是连接到一个文件,还是连接到一个硬件,还是连接到一个socket,还是其它的任何能连接的东西,反正channel就代表了这个连接,这个连接也代表了自己本身的属性,具体来说就是我们常见的FileChannelByteChannelDataGramChannelServerSocketChannelSocketChannel等各个具体的channel、接口或抽象类,功能上针对各自的应用场景做了扩展而已。

buffer用来代表客人取食物以及放回餐盘的缓冲区,也即channel的缓冲区,channel要想拿到东西,或者放一个东西,必须要和buffer打交道,哎,好像附带着把连接和读写的功能给解耦了呀,想之前的io包,stream还需要负责读或写的功能,看现在功能解耦之后,好一个清爽的世界呀!一句话:channel就是连接,读写要找buffer,搞定。

但这样一来,buffer的速度还是不够高呀,这个时候,那几个java开发人员相视一笑,嘿嘿,炫技的时刻到了,一通输出之后,写出来了76个buffer相关的类,全面覆盖了各种类型的buffer,如常用的ByteBufferCharBuffer等,基于直接内存的各种类型的buffer,基于堆的各种类型的buffer,以及使用内存映射的MappedByteBuffer等,还有基于ByteBuffer来读取转换为其它各种类型数据的工具类。这样既解决了读写的性能问题,又解决了数据的转换问题,还顺便解决了大文件的读写问题。把简单的思路玩成别人难人比肩的高度,这就是大神!

单单这样就够了吗?no no no,too young to simple了,不是说原先的io包不好用吗?不是说原先的文件管理功能太简单吗?干脆玩彻底一点,把原来的io包的功能也顺便扩展一下,加入nio中的channel,悄悄的让它也加入到nio的世界中来,还写了一个强大的Files工具类,把大家想要的文件管理相关的功能全都加上了,又加一个路径管理工具Path,让大家用起来更方便,这下没有什么好吐槽的了吧!大神们终于可以休息一段时间了。

nio的应用示例

上面讲了这么多,好像没有看到一行代码,那么到底应该怎么使用nio呀?按照军哥的惯例,肯定不会让大家失望的,下面依然没有一行代码。

走起。

读写文件示例

要想读取文件,肯定得知道文件的位置,也就是文件的路径,比如"/xx/xx/xx.log",有了路径,就可以实例化一个FileInputStream了,有了InputStream,通过它的getChannel方法,就可以获取到这个文件对应的FileChannel了,有了channel,就要考虑buffer了,这里暂时用不到selector,所以不用管它,怎么创建一个buffer呢?java已经替我们想好了,ByteBuffer类中有现成的方法allocate,想用直接内存?没有关系,换个方法allocateDirect就搞定了,有了buffer,直接调用channel中的read方法,把buffer传进去,让它读到这个buffer中不就可以了嘛!写文件的话是同样的思路,调用 channel中的write方法,就可以把buffer中的数据写进去了,在这里就不再多说了。

服务端socket读写示例

socket通常代表网络连接,对一个socket执行读操作,就相当于读取用户传过来的数据,对一个socket执行写的操作,就相当于给用户发送数据。

以服务器程序为例,想用nio来操作socket,就需要先有一个channel,服务端的channel已经有现成的实现了ServerSocketChannel,直接调用它里面的open方法,就可以创建一个服务器端的socketChannel了,有了channel还不够,还需要为它配置参数,先用它的configureBlocking方法把它配置成非阻塞的,再用它里面的bind方法来配置一个它要监听的地址和端口,这时候就需要用到selector了,以便让服务器在有连接或其它相关事件发生时,来通知我们的程序。所以我们先调用Selector中的open方法,来获取一个selector,再使用channel中的register方法,把我们关心的事件注册到selector上,比如客户端连接事件,注册好之后,就可以循环调用selector中的select方法来接收事件列表了,此时代码就会阻塞了,直到有事件进来,一旦有连接事件进来,就可以通过之前创建的ServerSocketChannel中的accept方法,来获取到客户的连接对应的SocketChannel了,终于可以看见客户端了,有了客户端的channel,先把它设置为非阻塞,再把它也注册进selector中,此时可以关注它的可读事件,一旦有客户端发来了数据,服务器就会通过selector来通知我们,通过selectorselectedKeys方法就可以获取到事件列表,然后区分出可读的事件类型就可以进行读取了,读取方法就不用多说了吧,使用channel中的read方法,读到byteBuffer里就可以了。

在这里,selectorchannelbuffer全都聚齐了。并且实现了一个服务器的功能。

至于向客户端发数据,应该也不用多说了吧,通过channelwrite方法,把buffer中的数据写进去,就相当于给客户端发送数据了。

客户端socket读写示例

客户端向服务器发送及接收数据,在这里也大概说一下吧,思路和服务器端的一样,只不过客户端使用的channelSocketChannel,它也不需要bind,而是需要调用connect方法来连接到远程服务器,同样也需要selector,把连接事件先绑定上,等到连接成功后,再把可读事件也绑定到selector上,等到服务器有数据发过来时,就可以通过buffer来接收数据了,向服务器发送数据就更简单了,还是调用channel中的write方法就可以了。


现在,你已经对java的nio有比较系统的了解了吧,但这样处理socket是不是太麻烦了,有没有简单一点的方法呢?当然有了,可以使用netty嘛!想了解netty及更多java相关技术,请微信搜索并关注 “五彩石编程”公众号,军哥的文章将会持续同步更新。


下课

  • 43
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值