Java NIO

概述

这段时间看了不少java NIO的教程,网上找到的基本都是大同小异。源头都出自一位老外的技术分享:NIO教程。基于这些知识点结合自己的理解在这里阐述一下。

三大核心组件

NIO中由三大核心组件,分别是Channel、Buffer、Selector。

Channel和Buffer

Channel和Buffer就类似于java IO中的Stream,NIO通过Channel读取数据到Buffer中或者从Buffer中写入数据到Channel,而IO中的Stream要么只能读取要么只能写入。Channel的读写可以设置成非阻塞。Channel和Buffer相比于Stream的不同点:

  • 同一个Buffer有写入模式和读取模式(flip方法切换),同一个Stream只读或者只写
  • Channel可以设置成非阻塞,Stream都是阻塞的

Buffer

NIO中的Buffer,可以理解为缓冲区,Channel中的数据必须通过Buffer流转。我们可以往Buffer中写数据,也可以从Buffer中读数据。

Buffer中定义了4个属性:mark、position、limit、capacity,并且保证mark <= position <= limit <= capacity。每个属性代表不同的意思:

  • position,用于保存读写的位置,初始为0,写一个数据或者读一个数据,position就往后移动一个位置,position++,调用flip方法position=0;
  • limit,是可写或者可读的限制,初始为capacity,调用flip方法会将position赋值给limit,limit=position;
  • capacity,容量初始设置就不变了
  • mark,用来标记用,调用mark方法设置标记mark=position,调用reset方法将position重置到上次设置的位置position=mark;

由于Buffer的设计限制,Buffer只能要么写要么读,要正确的将数据从Buffer中读取出来,记得必须调用flip方法。

  1. Buffer写数据
  2. 调用flip方法
  3. Buffer读取数据
  4. 调用clear方法

Buffer中几个方法都是被final修饰,不能被子类重写

flip

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

clear

清空数据只是几个属性被重置成初始状态


public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

rewind

rewind方法重置position,表示重写,或者重读

public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}

Selector

Selector对象用来监控多个Channel的状态,一次select能够将已经准备就绪的Channel挑选出来,然后可以对这些Channel进行相应的IO操作。这样就用一个Selector对象就能管理多个Channel连接了。

传统的Socket中,一个ServerSocket对象每次accept一个Socket连接,然后使用这个Socket的InputStream和OutputStream分别进行读写,直到这个Socket关闭之前这都是阻塞的。特别处理长连接的情况下,在这个Socket关闭之前,可能大部分时间内都可能没有数据进行传输,但是还是必须阻塞着。为了避免被一个Socket阻塞而影响下一个Socket的accept,一般的做法都是用一个Thread处理一个Socket,这样的做法虽然解决了阻塞的问题,但是衍生出另一个问题,就是会随着Socket的增加而产生很多的Thread,而且大部分时间内这些Thread都是没什么事情可做,非常浪费系统资源。

为了解决传统Socket中的问题,NIO定义了Selector。一个Selector能够监控多个Channel的状态,不管是ServerSocketChannel还是SocketChannel,都可以注册到一个Selector上。Selector在一次select后,会把已经准备就绪的Channel挑选出来,然后可以对这些Channel进行相应的IO操作。每个Channel的就绪状态分别为read、write、connect、accept,根据不同状态处理不同的方法,处理完了之后,下一次如果又准备就绪了还会被Selector再一次select出来,这样每个Channel都能被Selector多次复用。而且就算是长连接每次也只处理一种事件类型,这样也不会造成阻塞,只要一个线程就够了。这种模型就能作用只使用一个线程就可以操作大量的客户端连接。

Selector和Reactor模式和IO多路复用,这三者到底是什么关系?

register方法

需要使用Selector监控,必须首先注册,通过调用SelectableChannel.register()方法。Channel调用这个方法,使用必须配置为非阻塞的,并且注册的时候需要指定这个Channel所关注的事件,分为read、write、connect、accept。当这个Channel所关注的事件准备就绪之后,Selector才会通过select方法返回。比如,一个Channel的read事件就绪了,但是在注册到Selector的时候没有关注read类型,那么Selector也不会select出来,可以查看Channel的translateReadyOps方法。

SelectionKey

调用register方法返回SelectionKey对象,对象关联了Channel、Selector、还包括Channel所关注的事件(interestOps),和这个Channel准备就绪的事件(readyOps)。

select方法

Selector对象通过一次select方法,返回准备就绪满足条件的Channel数量,如果>0,后续调用selectedKeys方法返回SelectionKey的set集合。通过迭代set,对不同的事件进行不同的处理。

select方法是一个阻塞方法,知道有Channel就绪了才会返回,也可以被wakeup方法唤醒。select方法有一个重载方法,select(timeout),超时返回。selectNow方法非阻塞,都没有可用就绪的Channel就返回0。Selector的select方法,每次返回这个调用以来的准备就绪的Channel,比如第一次select有一个就绪,返回1,再一次调用又有一个就绪,还是返回1

Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> 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
    }
    // 必须remove掉,因为SelectionKey不会自己remove,我们处理完一个Channel必须remove掉
    keyIterator.remove();
}



 

转载于:https://my.oschina.net/cregu/blog/2252688

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值