通常的NIO是指的NON Blocking IO, 非阻塞IO的缩写.
java中的NIO, 官方释义是 New IO. 也有人说就是NON Blocking IO. 此处感觉无所谓.
在java中的NIO编程与传统IO相比的区别是:
传统IO是面向流的,而NIO是面向块的.
这块我得理解是, 都是渴了去接水, 传统IO是到水龙头那地儿张着嘴喝饱再走. 而NewIO是把水杯(Buffer缓冲区)放在水龙头下面,然后找个人(Selector)帮自己看着水有没有接满, 接满了告诉自己一声,收到哪个人的信号后NewIO再来拿杯子一饮而尽, 这样的好处就是接水的时间NewIO就可以忙自己的事情了. 此处如果没有Selector这个对象的话,NIO也是需要拿着杯子等着了,所以没有Selector的话也是阻塞的. 可以得出下面结论:
在java中NIO编程可以是阻塞的, 也可以是非阻塞的.
阻塞情况: 比如文件相关的File相关的Channel都是阻塞式的, 在读取文件时 只用到了NIO包下面的缓冲区 Buffer(一般用的是ByteBuffer,除了boolean类型的基础类型以及键值对等等都有相应的Buffer提供), 以及FileChannel. 线程走到这的时候依然是阻塞中的. 如果是套接字编程的话, 如果不引用Selector对象而只是用NIO包下面提供的ServerSocketChannel以及SocketChannel对象, 以及缓冲区对象 ByteBuffer的话其实线程也是阻塞的.
非阻塞情况: 只有当在网路编程中(因为File相关的Channel不是实现的Selectable接口,也就没法用Selector去监听)才能实现非阻塞的编程. 这里需要注意,所有的在非阻塞式编程中用到的Channel都需要调用configureBlocking(false)方法来设置为非阻塞模式, 然后在Channel的register方法中注册Selector选择器的监听,这样就可以实现非阻塞的网络NIO编程了.
另外NIO还有个特性就是能对缓冲区对象操作,具体的方法大概有以下几种:
Buffer.flip() 重置当前读写游标position为0,同时将读写的结束位置limit放在游标重置之前的位置.(写入Channel(也就是读取Buffer)之前调用,这样读Buffer的时候就可以从头开始读到写入结束的位置了)
Buffer.clear() 重置当前读写游标position为0,将读写结束位置limit放在缓冲区最后一位capacity处(重写Buffer之前调用,这样的游标再次写入会覆盖之前的数据,就当之前的数据不存在)
Buffer.compact() 作用类似于clear()方法,但并不会重写之前的数据, 会把读写游标置于之前数据的limit之后,也就是用Buffer剩余空间做新的缓冲区, 适用于之前数据还需要用的情况,这时候可以配合mark()和reset()(?reset方法好像是,记不请了) 来读取之前的数据.
具体的Buffer使用可以参考这位大牛写的博客:
下面是我从视频中临摹来大牛的一段NIO实现非阻塞套接字编程的代码:
public class TestNonBlockingNIO {
//客户端
@Test
public void client() throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.切换成非阻塞模式
sChannel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024);
//4.发送数据给服务端
Scanner scan= new Scanner(System.in);
while(scan.hasNext()){
String str=scan.next();
buf.put((new Date().toString()+"\n"+str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
//5.关闭通道
sChannel.close();
}
//服务端
@Test
public void server() throws IOException {
//1.获取通道
ServerSocketChannel ssChannel=ServerSocketChannel.open();
//2.切换成非阻塞模式
ssChannel.configureBlocking(false);
//3.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector = Selector.open();
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询式的获取选择器上已经 "准备就绪" 的事件
while (selector.select()>0){
//7.获取当前选择器中所有注册的"选择键(已就绪的监听事件)"
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
while (it.hasNext()){
//8.获取准备就绪的事件
SelectionKey sk=it.next();
//9.判断具体是什么时间准备就绪
if(sk.isAcceptable()){
//10.若"接受就绪",获取客户端连接
SocketChannel sChannel = ssChannel.accept();
//11.切换非阻塞模式
sChannel.configureBlocking(false);
//12.将该通道注册到选择器上
sChannel.register(selector,SelectionKey.OP_READ);
}else if(sk.isReadable()){
//13.获取当前选择器上"读就绪"状态的通道
SocketChannel sChannel=(SocketChannel)sk.channel();
//14.读取数据
ByteBuffer buf=ByteBuffer.allocate(1024);
int len=0;
while((len=sChannel.read(buf))>0){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
//15.取消选择器
it.remove();
}
}
}
}