NIO学习资料

来源:互联网 发布:淘宝闹鬼的古着店 编辑:程序博客网 时间:2024/05/16 18:14

http://weixiaolu.iteye.com/blog/1479656

Java NIO提供了与标准IO不同的IO工作方式: 

Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。

IO面向流与NIO面向缓冲 

Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。 

阻塞与非阻塞IO 

Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。 

NIO的channel

既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
通道可以异步地读写。
通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

这些是Java NIO中最重要的channel的实现: 
FileChannel:从文件中读写数据。
DatagramChannel:能通过UDP读写网络中的数据。
SocketChannel:能通过TCP读写网络中的数据。
ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。


以下是Java NIO里关键的Buffer实现: 
ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。 

向Buffer中写数据 
写数据到Buffer有两种方式: 
从Channel写到Buffer。
通过Buffer的put()方法写到Buffer里。
从Channel写到Buffer的例子 :int bytesRead = inChannel.read(buf); //read intobuffer.
通过put方法写Buffer的例子: buf.put(127);

从Buffer中读取数据 
从Buffer中读取数据有两种方式: 
从Buffer读取数据到Channel。
使用get()方法从Buffer中读取数据。
从Buffer读取数据到Channel的例子: int bytesWritten =inChannel.write(buf);//read from buffer intochannel.
使用get()方法从Buffer中读取数据的例子:byte aByte = buf.get();

代码如下:
Name: Anna
Age: 25
Email: anna@mailserver.com

NIO的读取代码如下:

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

注意第二行,从通道读取字节到ByteBuffer。当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内。你所知道的是,该缓冲区包含一些字节,这使得处理有点困难。 
假设第一次 read(buffer)调用后,读入缓冲区的数据只有半行,例如,“Name:An”,你能处理数据吗?显然不能,需要等待,直到整行数据读入缓存,在此之前,对数据的任何处理毫无意义。 

所以,你怎么知道是否该缓冲区包含足够的数据可以处理呢?好了,你不知道。发现的方法只能查看缓冲区中的数据。其结果是,在你知道所有数据都在缓冲区里之前,你必须检查几次缓冲区的数据。这不仅效率低下,而且可以使程序设计方案杂乱不堪。

所以NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。 

如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。同样,如果你需要维持许多打开的连接到其他计算机上,如P2P网络中,使用一个单独的线程来管理你所有出站连接,可能是一个优势;如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合。

NIO的选择器(Selectors) 

Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。下面是单线程使用一个Selector处理3个channel的示例图:  

(1)Selector的创建 :

通过调用Selector.open()方法创建一个Selector,Selector selector = Selector.open();

(2)向Selector注册通道 :

为了将Channel和Selector配合使用,必须将channel注册到selector上。

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);//与Selector一起使用时,Channel必须处于非阻塞模式下。

Selector监听Channel时对感兴趣事件做了四种常量值监听

SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

可对多个同时监听:int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;


(3)SelectionKey对象

当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含几个属性:

interest集合:interest集合是你所选择的感兴趣的事件集合。
ready集合:ready 集合是通道已经准备就绪的操作的集合。
Channel:Channel channel= selectionKey.channel();
Selector:Selector selector = selectionKey.selector();

(4)通过Selector选择通道 

一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。 
一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。
代码如下:
// 获得selector中选中的项的迭代器,选中的项为注册的事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
注意每次迭代末尾的keyIterator.remove()调用。

(5)wakeUp() 

某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。 如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。 

(6)close() 

用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。 




0 0
原创粉丝点击