Java nio 之 概述

来源:互联网 发布:python的垃圾回收机制 编辑:程序博客网 时间:2024/05/20 07:17

java nio 由以下部分组成:

    1 Channels    2  Buffers  3 Selector

     

Channel 和 Buffer

基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示:

Channel和Buffer有好几种类型。下面是JAVA NIO中的一些主要Channel的实现:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

正如你所看到的,这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。

与这些类一起的有一些有趣的接口,但为简单起见,我尽量在概述中不提到它们。本教程其它章节与它们相关的地方我会进行解释。

以下是Java NIO里关键的Buffer实现:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。

Java NIO 还有个 MappedByteBuffer,用于表示内存映射文件。

Channel

FileChannel 从文件中读写数据。

DatagramChannel 能通过UDP读写网络中的数据。

SocketChannel 能通过TCP读写网络中的数据。

ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

下面是一个FileChannel的示例

复制代码
public class FileChannelTest {    public static void main(String[] args) throws IOException {        RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");        FileChannel inChannel = aFile.getChannel();     
//涉及到的buffer的方法稍后解释 ByteBuffer buf
= ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf); while (bytesRead != -1) { //make buffer ready for read buf.flip(); while (buf.hasRemaining()) { System.out.print((char) buf.get());// read 1 byte at a time }
buf.clear();//buf.compact();也可以 bytesRead
= inChannel.read(buf); } aFile.close(); }}

Buffer

为了理解Buffer的工作原理,需要熟悉它的三个属性:

  • capacity
  • position
  • limit

在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。

当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

 

clear方法就是让position设回0,limit与capacity相等。

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

 

flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。

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

 

compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

复制代码
  public ByteBuffer compact() {        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());        position(remaining());        limit(capacity());        discardMark();        return this;    }

Scatter/Gather

scatter/gather用于描述从Channel中读取或者写入到Channel的操作

分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。

 

应用场景:例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体

 

Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定)。

ByteBuffer header = ByteBuffer.allocate(128);ByteBuffer body   = ByteBuffer.allocate(1024);ByteBuffer[] bufferArray = { header, body };channel.read(bufferArray);

 

复制代码
ByteBuffer header = ByteBuffer.allocate(128);ByteBuffer body   = ByteBuffer.allocate(1024);//write data into buffersByteBuffer[] bufferArray = { header, body };channel.write(bufferArray);
复制代码

 

FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中,下面是一个简单的例子:

复制代码
RandomAccessFile fromFile = new RandomAccessFile("data/fromFile.txt", "rw");FileChannel      fromChannel = fromFile.getChannel();RandomAccessFile toFile = new RandomAccessFile("data/toFile.txt", "rw");FileChannel      toChannel = toFile.getChannel();long position = 0;long count = fromChannel.size();//toChannel.transferFrom(fromChannel, position, count);也可以fromChannel.transferTo(position, count, toChannel);
复制代码

 




Selector

Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。

这是在一个单线程中使用一个Selector处理3个Channel的图示:

要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接,Selector能够处理多个通道。

复制代码
Selector selector = Selector.open();
//FileChannel不能切换到非阻塞模式,所以这边不能使FileChannelchannel.configureBlocking(
false);//与Selector一起使用时,Channel必须处于非阻塞模式下SelectionKey key = channel.register(selector, SelectionKey.OP_READ); //除了注册读,还可以注册connect,accept,read,write事件while(true) { int readyChannels = selector.select(); //阻塞到至少有一个通道就绪,还有select(long timeout)超时就不阻塞,selectNow()不阻塞,没有就返回0,当然打断阻塞还有wakeUp()方法,可以用另外一个线程调用这个方法,操作同一个selector对象即可 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.
     //SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等
} 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 }
  //Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。 keyIterator.remove(); }}
复制代码

 

现在能看到的情况是,一个请求过来,到Selector这边,selector从注册的通道中选择就绪的通道,然后找到具体的通道处理这个请求。

用一个selector线程来安排所有的channel!

当然为了并发,可以用多个selector,然后不同的channel来注册。这样就有了反向代理的感觉,selector就是反向代理服务器上的线程!


Java NIO与IO

 

Java NIO与IO之间主要差别

IO                NIO面向流            面向缓冲阻塞IO            非阻塞IO无                选择器

Java NIO的缓冲导向方法是数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。NIO设计中多了buffer,传统IO如果要这个效果,需要自行定义操作buffer。

Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。

Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器。

 

在IO设计中,我们从InputStream或 Reader逐字节读取数据。 readline()阻塞直到整行读完

NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。

 

如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。

如果你需要维持许多打开的连接到其他计算机上,如P2P网络中,使用一个单独的线程来管理你所有出站连接,可能是一个优势。

Java NIO: 单线程管理多个连接,如下图

如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合。

Java IO: 一个典型的IO服务器设计- 一个连接通过一个线程处理,如下图




0 0