NIO的使用

1.为什么要用?

nio比I/O快,nio使用块操作,而不是流,它将最耗时的,填充和缓冲区存取交给OS而提高速度

    (流操作是一个字节一个字节的读,虽然简单,而且可以容易的附加过滤器,但是效率低)

2 nio简介

    buffer和channel是nio的核心,任何对象到(去)任何地方,都要通过channel,而buffer相当于一个容器,任何想去channel的,都要先通过buffer

   nio中的所有数据都要先使用buffer(不论读写),buffer相当于一个数组,提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程

  buffer的各个实现,最常用的Bytebuffer,

每个primate类型都对应一个buffer,Intbuffer,LongBuffer,CharBuffer...

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

LongBuffer

FloatBuffer

DoubleBuffer

缓冲区的容量 是它所包含的元素的数量。缓冲区的容量永远不会为负并且从不会更改。

缓冲区的限制 是不应读取或写入的第一个元素的索引。缓冲区的限制永远不会为负,并且永远不会大于其容量。

缓冲区的位置 是下一个要读取或写入的元素的索引。缓冲区的位置永远不会为负,并且永远不会大于其限制。 

 

0 <= 标记 <= 位置 <= 限制 <= 容量

 

clear() 使缓冲区做好了新序列信道读取或相对 put 操作的准备:它将限制设置为容量大小,将位置设置为零。

flip() 使缓冲区做好了新序列信道读取或相对 get 操作的准备:它将限制设置为当前位置,然后将该位置设置为零。

rewind() 使缓冲区做好了重新读取已包含的数据的准备:它使限制保持不变,并将位置设置为零。 

 

Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流。

正如前面提到的,所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。  

通道与流的不同之处在于通道是双向的,而流不是

 

在 NIO 系统中,任何时候执行一个读操作,您都是从通道中读取,但是您不是 直接 从通道读取。因为所有数据最终都驻留在缓冲区中,所以您是从通道读到缓冲区中。

因此读取文件涉及三个步骤:(1) 从 FileInputStream 获取 Channel,(2) 创建 Buffer,(3) 将数据从 Channel 读到 Buffer 中。 

 

第一步是获取通道。我们从 FileInputStream 获取通道:

 

FileInputStream fin = new FileInputStream( "readandshow.txt" );

FileChannel fc = fin.getChannel();

 

下一步是创建缓冲区:

 

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

 

最后,需要将数据从通道读到缓冲区中,如下所示:

 

fc.read( buffer );

clear() 方法重设缓冲区,使它可以接受读入的数据。 flip() 

方法让缓冲区可以将新读入的数据写入另一个通道

状态变量,每次buffer的读/写操作,都会改变buffer的状态,从而能监控buffer

1.position

2.limit

3.capacity

position 指定下一个数据将放到buffer的哪个位置(数组的索引)---从channel读

从channel取---position将表示已经从buffer中读了几个,将要读的哪个位置索引

 

limit变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。 

 

position总是小于或者等于limit

 

capacity 底层数组大小,limit<=capacity

 

 

现在我们要将数据写到输出通道中。在这之前,我们必须调用 flip()方法。这个方法做两件非常重要的事: 

它将limit设置为当前position。 

它将 position设置为 0。 

 

读完后,使用clear()方法

1.将limit设的和capacity一样大

2.将position设为0

 

 

 

get() 方法

 

 

 

ByteBuffer类中有四个get() 方法:  

1.byte get();

2.ByteBuffer get(byte dst[]);

3,ByteBuffer get(byte dst[],int offset,int length);

4.byte get(int index)

 

第一个方法获取单个字节。第二和第三个方法将一组字节读到一个数组中。第四个方法从缓冲区中的特定位置获取字节。那些返回 

ByteBuffer的方法只是返回调用它们的缓冲区的this值。 

 

此外,我们认为前三个get()方法是相对的,而最后一个方法是绝对的。 相对 意味着 

get()操作服从limit ,position值 — 

更明确地说,字节是从当前position读取的,而position在 

get之后会增加。另一方面,一个 绝对 方法会忽略limit和 

position值,也不会影响它们。事实上,它完全绕过了缓冲区的统计方法。 

 

上面列出的方法对应于ByteBuffer类。其他类有等价的get()

方法,这些方法除了不是处理字节外,其它方面是是完全一样的,它们处理的是与该缓冲区类相适应的类型。 

 

 

put()方法

 

byte put(byte b)

BufferByte put(byte[] src)

ByfferByte put(byte[] src,int offset,int length)

BufferByte put(BufferByte buffer)

byte put (int index,byte b)

 

一个循环使用

while(true){

buffer.clear();

int r = fcin.read(buffer);

if(r == -1)break;

buffer.flip();

fout.write(buffer);

}

 

buffer,分配大小 allocate(1024)

还可以转换一个数组,来做buffer

byte array = new byte[1024];

        ByteBuffer buffer = ByteBuffer.wrap(array);

 

buffer分片共享数据

buffer.position(3)

buffer.limit(7)

ByteBuffer slice = buffer.slice();

创建一个3-6的子缓冲区,共享数据

 

分散和聚集的应用,把数据读入多个buffer...

 

非阻塞的I/O读取,为异步I/O

核心为selector,是注册感兴趣I/O事件,当这些事件发生时,通知你

Selector selector = Selector.open();

然后,对不同的channel对象,registor(),注册对这些channel中I/O事件(如果感兴趣)

registor()的第一个参数总是selector

 

为了接受连接,需要一个ServerSocketChannel(监听每个端口都需要一个)

ServerSocketChannel ssc = ServerSocketChannel.open();

ssc.configureBlocking( false );

//绑定端口

ServerSocket ss = ssc.socket();

InetSocketAddress address = new InetSocketAddress( ports[i] );

ss.bind( address );

 

SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );

register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 accept 事件,也就是在新的连接建立时所发生的事件。这是适用于 ServerSocketChannel 的唯一事件类型。

请注意对 register() 的调用的返回值。 SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。 

 

现在已经注册了我们对一些 I/O 事件的兴趣,下面将进入主循环。使用 Selectors 的几乎每个程序都像下面这样使用内部循环: 

 

 

int num = selector.select();

 

Set selectedKeys = selector.selectedKeys();

Iterator it = selectedKeys.iterator();

 

while (it.hasNext()) {

     SelectionKey key = (SelectionKey)it.next();

     // ... deal with I/O event ...

}

 

首先,我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时, select() 方法将返回所发生的事件的数量。 

 

接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个 集合 。 

 

我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。 

 

 

 

 

 

程序执行到这里,我们仅注册了 ServerSocketChannel,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件 

if ((key.readyOps() & SelectionKey.OP_ACCEPT)

     == SelectionKey.OP_ACCEPT) {

     // Accept the new connection

     // ...

}

 

可以肯定地说, readOps() 方法告诉我们该事件是新的连接。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值