May 7, 2015 ruochenxing java nio

java NIO

Java标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。

Java NIO 由以下几个核心部分组成:

Channels
Buffers
Selectors

Java NIO的Channel有点象流,但又有些不同:

既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
通道可以异步地读写。
通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

下面是JAVA NIO中的一些主要Channel的实现:

FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel

这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。

Channel 示例:

	RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
	FileChannel inChannel = aFile.getChannel();
	ByteBuffer buf = ByteBuffer.allocate(48);
	int bytesRead = inChannel.read(buf);
	while (bytesRead != -1) {
		System.out.println("Read " + bytesRead);
		buf.flip();//将buffer的写模式转换成读模式
		while(buf.hasRemaining()){
			System.out.print((char) buf.get());
		}
		buf.clear();
		bytesRead = inChannel.read(buf);
	}
	aFile.close();

Java NIO中的Buffer用于和NIO通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的。 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

以下是Java NIO里关键的Buffer实现: ByteBuffer MappedByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer LongBuffer ShortBuffer

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

使用Buffer读写数据一般遵循以下四个步骤:

1)写入数据到Buffer
2)调用flip()方法
3)从Buffer中读取数据
4)调用clear()方法或者compact()方法

当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

下面是一个使用Buffer的例子:

	RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
	FileChannel inChannel = aFile.getChannel();
	//create buffer with capacity of 48 bytes
	ByteBuffer buf = ByteBuffer.allocate(48);
	int bytesRead = inChannel.read(buf); //read into buffer.
	while (bytesRead != -1) {
		buf.flip();  //make buffer ready for read
		while(buf.hasRemaining()){
			System.out.print((char) buf.get()); // read 1 byte at a time
		}
		buf.clear(); //make buffer ready for writing
		bytesRead = inChannel.read(buf);
	}
	aFile.close();

Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在 一个聊天服务器中。 要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。 Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

完整的示例 这里有一个完整的示例,打开一个Selector,注册一个通道注册到这个Selector上(通道的初始化过程略去),然后持续监控这个Selector的四种事件(接受,连接,读,写)是否就绪。

	Selector selector = Selector.open();//调用Selector.open()方法创建一个Selector
	channel.configureBlocking(false);//与Selector一起使用时,Channel必须处于非阻塞模式下
	//这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
	SelectionKey key = channel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
	//register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。
	//返回一个SelectionKey,这个对象包括interest集合,ready集合,Channel,Selector,附加的对象(可选)
	while(true) {
		int readyChannels = selector.select();//select()方法阻塞到至少有一个通道在你注册的事件上就绪了。
		//返回的int值表示有多少通道已经就绪
		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.
			} 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
			}
			keyIterator.remove();//每次迭代末尾的keyIterator.remove()调用。
			//Selector不会自己从已选择键集中移除SelectionKey实例。
			//必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。
		}
	}

Share this post

Search widget

Timeline

Friendly Links