一、NIO的核心部分
- 通道(channel):用于读操作和写操作,主要负责数据的“运输”。相当于传统IO中的stream,不过stream是单向的。
- 缓冲区(buffer):一个容器,用于存储数据。
- 选择器(selector):核心类,能够检测多个注册的通道上是否有事件发生,因此一个线程可以管理多个通道。
二、为什么使用NIO
使用NIO主要就是为了提高IO的速度
- 传统IO是基于字节流和字符进行操作的,而NIO是基于通道(channel)和缓冲区(buffer)进行操作的,这样一来数据的偏移操作非常方便。
- 传统IO是阻塞的,如果发出IO请求后数据没有就绪,会处于阻塞状态,而NIO通过通道操作数据,因此一个通道无数据可读,可以切换到另一个通道。
- NIO有选择器,因此单线程可以操作多个通道。
三、简单的NIO读和写例子
-
从文件中读取
与传统IO不同的是,使用NIO从文件中读取主要分为三步:
(1)从FileInputStream中获取channel;FileInputStream fin = new FileInputStream( "readandshow.txt" ); FileChannel fc = fin.getChannel();
(2)创建buffer;
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
(3)将数据从channel读到buffer中。
fc.read( buffer );
-
写入文件
类似的,利用NIO写入文件也分为三步:
(1)从FileInputStream中获取channel;FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); FileChannel fc = fout.getChannel();
(2)创建一个缓冲区并在其中放入一些数据;
ByteBuffer buffer = ByteBuffer.allocate( 1024 ); for (int i=0; i<message.length; ++i) { buffer.put( message[i] ); //从message数组中取出放入buffer } buffer.flip(); //将buffer由写模式切换为读模式
(3)将数据从buffer写到通道中。
fc.write( buffer );
四、buffer内部细节
- flip()函数源码
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
这里可以注意到limit,position和mark三个变量
(1) capacity:表明可以储存在缓冲区中的最大数据容量。
(2) position:下一个可插入的位置(写buffer时)或者下一个可读的位置(读buffer时)。
(3) limit:最多能写多少数据(写buffer时,相当于capacity),可以读多少数据(在从通道读入缓冲区时)。
(4) mark:标记,记录当前position的位置,可以通过reset()恢复到mark的位置。
五、选择器
- selector的创建
Selector selector = Selector.open();
- 因为选择器是用于管理通道的,因此需要将通道注册到相应的选择器上
channel.configureBlocking(false);
//使用selector必须保证channel是非阻塞的模式SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
注意register的第二个参数表示对选择器对什么事件感兴趣。
而返回值记录了包括channel,buffer,interest集合和ready集合等。 - 通过selector选择通道
可以使用select()函数进行选择,select()方法返回的int值表示有多少通道已经就绪。 -
selectedKeys()函数
一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。这里的selectorKey的就是之前注册到该selector的通道。 -
示例程序
Selector selector = Selector.open(); //开启选择器 channel.configureBlocking(false); //非阻塞模式 SelectionKey key = channel.register(selector, SelectionKey.OP_READ);//注册 while(true) { int readyChannels = selector.select(); //选择通道 if(readyChannels == 0) continue; Set selectedKeys = selector.selectedKeys(); //获取通道 Iterator keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); //需要自己移除处理完的通道 } }
六、NIO和IO的使用场景
NIO具备一定的优点,但并不是说传统IO就一无是处
(1)在处理数据上,如果遇到逐行处理的情况,如:
Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890
传统的IO可以这样写:
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String nameLine = reader.readLine();
String ageLine = reader.readLine();
String emailLine = reader.readLine();
String phoneLine = reader.readLine();
但如果使用NIO:
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
你就不知道缓冲区内是否刚好是一行数据,这样处理起来会比较麻烦。
(2)如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。
如果你需要维持许多打开的连接到其他计算机上,如P2P网络中,使用一个单独的线程来管理你所有出站连接,可能是一个优势。
但如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合。
学习参考:http://ifeve.com/java-nio-all/ Java NIO 系列教程
https://www.ibm.com/developer... NIO入门
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。