java nio 图解_图解Java NIO

目录:

NIO结构

NIO与传统IO异同

NIO使用步骤

NIO代码

ByteBuffer难点解析

1:NIO结构:

Channel:通道,连接客户端和服务端的一个管道,管道内可以双向传输数据。

Selector:选择器,可以想象成一个环状传送带,上面可以接入很多管道,selector还可以对每个管道设置感兴趣的颜色(连接(红色),读(黄色),写(蓝色),接收数据)。当Selector开始轮询的时候Selector这个传送带就一直转动,当某个管道被传送到感兴趣事件检查点的时候,selector会检查改管道当前颜色(即事件)之前是否被注册成了感兴趣颜色(事件),如果感兴趣,那么Selector就可以对这个管道做处理了,比如把管道传给别的线程,让别的线程完成读写操作。

ByteBuffer:字节缓冲区,本质上是一个连续的字节数组,Selector感兴趣的事件发生后对管道的读操作所读到的数据都存储在ByteBuffer中,而对管道的写操作也是以ByteBuffer为源头,值得注意的是ByteBuffer可以有多个。想想看,我们使用的所有基本类型都可以转换成byte字节,比如Integer类型占4字节,那么传递数字 1 ByteBuffer底层数组被占用了4个格子。

e6f18c74bc3b

2:与传统IO比较:

1:传统IO一般是一个线程等待连接,连接过来之后分配给processor线程,processor线程与通道连接后如果通道没有数据过来就会阻塞(线程被动挂起)不能做别的事情。NIO则不同,首先:在Selector线程轮询的过程中就已经过滤掉了不感兴趣的事件,其次:在processor处理感兴趣事件的read和write都是非阻塞操作即直接返回的,线程没有被挂起。

2:传统IO的管道是单向的,NIO的管道是双向的

3:两者都是同步的,也就是Java程序亲力亲为的去读写数据,不管传统IO还是NIo都需要read和write方法,这些都是Java程序调用的而不是系统帮我们调用的。NIO2.0里这点得到了改观,即使用异步非阻塞AsynchronousXXX四个类来处理。

3:使用NIO步骤:(服务端)

首先:创建一个传送带

然后:创建一个管道,设置管道为非阻塞,绑定端口

然后:把管道放到传送带上

再然后:启动传送带

其次:传送带感兴趣事件检查点查获一个感兴趣管道,转给其他线程对管道进行非阻塞读写

最后:全使用完,关闭管道

过程很清晰,跟我们现实世界中的传送带效果一样。

4:代码体现:

注意:只写服务器端关键步骤,客户端可以参考这些代码public class Server implements Runnable {

private Selector selector;

private ByteBuffer buffer = ByteBuffer.allocate(1024);

public Server(int port) {

try {

//1 创建一个传送带

selector = Selector.open();

//2 创建一个管道

ServerSocketChannel ssc = ServerSocketChannel.open();

//3 设置服务器通道为非阻塞方式

ssc.configureBlocking(false);

//4 绑定TCP地址

ssc.bind(new InetSocketAddress(port));

//5 把管道放到传送带上,并在传送带上注册一个感兴趣事件,此处传送带感兴趣事件为连接事件

ssc.register(selector, SelectionKey.OP_ACCEPT);

System.out.println("Server start, port:" + port);

} catch (IOException e) {

e.printStackTrace();

}

}public void run() {

while (true) {

try {

//1 启动传送带,开始轮询

selector.select();

//2 所有感兴趣事件的keys

SelectionKey Iterator keys = selector.selectedKeys().iterator();

//3 遍历所有感兴趣事件集合

while (keys.hasNext()) {

SelectionKey key = keys.next();

keys.remove();

if(key.isValid()) { //如果key的状态是有效的

if(key.isAcceptable()) { //如果key是阻塞状态,则调用accept()方法

accept(key);

}

if(key.isReadable()) { //如果key是可读状态,则调用read()方法

read(key);

}

}

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

private void accept(SelectionKey key) {

try {

//1 获取服务器通道

ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

//2 执行阻塞方法

SocketChannel sc = ssc.accept();

//3 设置阻塞模式为非阻塞

sc.configureBlocking(false);

//4 注册到多路复用选择器上,并设置读取标识

sc.register(selector, SelectionKey.OP_READ);

} catch (Exception e) {

e.printStackTrace();

}

}

private void read(SelectionKey key) {

try {

//1 清空缓冲区中的旧数据

buffer.clear();

//2 获取之前注册的SocketChannel通道

SocketChannel sc = (SocketChannel) key.channel();

//3 将sc中的数据放入buffer中

int count = sc.read(buffer);

if(count == -1) { // == -1表示通道中没有数据

key.channel().close();

key.cancel();

return;

}

//读取到了数据,将buffer的position复位到0

buffer.flip();

byte[] bytes = new byte[buffer.remaining()];

//将buffer中的数据写入byte[]中

buffer.get(bytes);

String body = new String(bytes).trim();

System.out.println("Server:" + body);

} catch (Exception e) {

e.printStackTrace();

}

}

public static void main(String[] args) {

new Thread(new Server(8379)).start();

}

}

其他:

涉及到ByteBuffer分类及ByteBuffer的读写这里就不过多介绍了,就是一些指针和模式的变动,主要是flip方法,调用flip方法之后的变化从写模式变成读模式

e6f18c74bc3b

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值