io编程中存在两个问题,io是阻塞的,并且保持多个链接的时候须要加入多线程来保持socket链接。这种方式比较浪费资源,由于每一个链接都须要一个线程来保持,这在链接比较多的时候是一个浪费的资源是至关严重的。而两个问题在nio编程中就很好的解决这个问题,可是相对来讲,对于编程的复杂度也相对有点提升。java
这里不去深刻的讨论nio编程的原理,只是基于实用的角度上作一些简单的介绍。在nio编程中,主要有三个组件比较重要:channel、buffer、selector。编程
channel:网络
通道,它和io中流有点类似,因此的源数据或者目标数据都是直接和channel打交道的。java nio中channel有如下几种实现,这些通道涵盖了TCP和UDP网络io:多线程
buffer:socket
缓冲区 , 他是直接与channel交互的,咱们若是要读取channle中的数据时,都是先把数据从channle中读入到buffer中;一样的,若是咱们要向channel中写数据的时候,也要先把数据写入到buffer中。java nio中有也有多个实现,他们不要为了针对不一样的数据类型:函数
注意:这里要介绍一个buffer中三个比较重要的方法,这三个方法在buffer读写的过程当中都颇有用。这三个方法都跟buffer中三个标记有关系:position、limit、capacity。position至关于指针,表示当前正在读写的位置;limit指的是界限,表示读写的时候界限;capacity表示容量,也就是buffer缓冲区的大小。为了弄明白这些方法,了解这三个标记是前提,说了这么多,这三个方法是:clear(); rewind(); flip()。这三个方法也就是调整这些标记位置,下面是这三个方法的源码,相信很好理解:post
java nio缓冲区中标志mark标记,使缓冲区可以记住一个位置并在以后将其返回。缓冲区的标记在mark( )函数被调用以前是未定义的,调用时标记被设为当前位置的值。reset( )函数将位置设为当前的标记值。若是标记值未定义,调用reset( )将致使InvalidMarkException异常。一些缓冲区函数会抛弃已经设定的标记(rewind( ),clear( ),以及flip( )老是抛弃标记)。若是新设定的值比当前的标记小,调用limit( )或position( )带有索引参数的版本会抛弃标记。如调用mark( )来设定mark = postion。调用reset( )设定position = mark。标记在设定前是未定义的(undefined)。这四个属性之间老是遵循如下关系: 0 <= mark <= position <= limit <= capacitythis
selector:线程
选择器,这个就是nio为了解决io中多链接必需要多线程来处理的问题。selector容许单线程处理多个channel,你打开多个链接就会有多个channel,而selector能够管理多个channel。值得注意的是,selector必需要和非阻塞的通道配合使用,也就是说selector不能和fileChannel一块儿使用,由于fileChannel不能配置非阻塞的模式。指针
java io和java nio的主要区别:
一、io是面向流的,nio是面向缓冲区的。这就意味着io只能从流中读入一个或者或者多个字节。而nio由于是面向缓冲区的,数据都是先读入到缓冲区,也就是上面buffer中,并且在须要的时候,还能够在缓冲区中先后移动来获取不一样位置的数据。可是也有个麻烦的地方,就是要检查缓冲区中是否含有本身要处理的数据。
二、io是阻塞的,nio是非阻塞的。io在数据读入或者写入的时候是阻塞的,直到数据处理完成为止。而nio则是非阻塞的,咱们从buffer中读取数据的时候是当即返回的,可是这里有个问题就是咱们没法保证缓冲区中是有数据的。可是好处就是,咱们读取数据的时候不会阻塞,能够干别的事情。
下图能够说明这个问题(左边的是nio,右边的是io):
下面就是一个基于NIO的socket编程的例子:
public class NioSocketServer { public static void main(String[] args) { try { ServerSocketChannel server = ServerSocketChannel.open(); //新建NIO通道 server.configureBlocking(false); //配置通道为非阻塞的 ServerSocket socket = server.socket(); //建立socket服务 socket.bind(new InetSocketAddress(9123)); //绑定端口号 Selector selector = Selector.open(); //建立选择器 server.register(selector, SelectionKey.OP_ACCEPT); //将服务注册到选择器上,而且配置关注的事件 //循环获取通道内是否有关注的事件 while(true) { //判断通道中事发后有关注的事件。若是 >1 则表示通道中有关注的事件 int num = selector.select(); if(num < 1) { continue; } Set selectKeys = selector.selectedKeys();//得到事件的key值 //遍历全部的事件 Iterator iterator = selectKeys.iterator(); //得到迭代器 while(iterator.hasNext()) { SelectionKey key = (SelectionKey) iterator.next(); //移走这次事件 iterator.remove(); //判断该事件是不是请求链接的事件 if(key.isAcceptable()) { //得到相应的socket channel SocketChannel client = server.accept(); System.out.println("接受来自" + client + "的请求!"); client.configureBlocking(false); //将通道配置成非阻塞的 ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[1024]); //建立缓冲区 //在此通道上注册写事件 SelectionKey keyClient = client.register(selector, SelectionKey.OP_WRITE); //通道执行事件 keyClient.attach(byteBuffer); }else if(key.isWritable()) { //得到此通道上的socket channel SocketChannel client = (SocketChannel) key.channel(); //得到当前事件下的socketChannel ByteBuffer outByteBuffer = (ByteBuffer)key.attachment(); //得到通道上绑定的byteBuffer //若是缓冲区存在数据,清除掉,防止数据粘连,重置一下 if(outByteBuffer.hasRemaining()) { outByteBuffer.clear(); } //往此通道上写数据,先写入byteBuffer中 outByteBuffer.put("hello world".getBytes()); outByteBuffer.flip(); client.write(outByteBuffer); } key.channel().close(); } } } catch (IOException e) { e.printStackTrace(); } } }