Java NIO简介

在JDK1.4时Sun发布了java.nio这个包,顾名思义N代表NEW,即新一代IO通道,相比较于旧的IO流,新的IO通道是同步非阻塞的,因为旧的IO流越来满足不了现在需求,我们需要一种全新的非阻塞方式的IO通道。所以NIO诞生了

NIO中主要分为三大模块: Buffer(缓冲区)、Channel(通道)、Selector(选择器)

其中Channel是最重要的一个模块,我们可以使用的通道有(FileChannel、SocketChannel、ServerSocketChannel等),由于Channel是双向的,它允许我们在将资源或文件写出到缓冲区、或者从缓冲区写入。因此在文件操作中,或者是套接字通信中,我们都可以使用Channel来完成。

Buffer是通道中不可缺少的一个组成部分,缓冲区作为数据信息的载体,它通常用来处理一些数据操作上的问题。

Selector是SelectableChannel 对象的多路复用器。我们可以通过某个套接字通道的 register 方法注册该通道时,定义所需要进行的操作OP_ACCEPT ( 用于套接字接受操作的操作集位) OP_CONNECT ( 用于套接字连接操作的操作集位)  OP_READ(用于读取操作的操作集位) OP_WRITE (用于写入操作的操作集位),当它执行select会阻塞,直到有请求为止,当执行selectedKeys方法时会返回SelectionKey类型的Set集合,通过迭代器迭代我们可以对以上四种状态进行分别处理。

例子一:展示SocketChannel和Selector的使用


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * java.nio.channels.Selector
 * SelectableChannel 对象的多路复用器。 
 * 可通过调用此类的 open 方法创建选择器,该方法将使用系统的默认选择器提供者创建新的选择器。
 * 也可通过调用自定义选择器提供者的 openSelector 方法来创建选择器。
 * 通过选择器的 close 方法关闭选择器之前,它一直保持打开状态。
 * 选择请求的类型(isAcceptable,isConnectable,isReadable,isWritable)分别执行相应的code
 */
public class SelectorTest {
	public static void main(String[] args) {
		try {
			//新线程执行服务端,selector.select()是阻塞线程的
			new Thread(new Runnable() {
				public void run() {
					ChannelServer.open();
				}
			}).start();
			//延迟两秒主(main)线程执行客户端
			TimeUnit.SECONDS.sleep(2);
			ChannelClient.send();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	//

	private static class ChannelServer {
		public static void open() {
			try {
				ServerSocketChannel ssc = ServerSocketChannel.open();

				// 调整此通道的阻塞模式。
				// 如果向一个或多个选择器注册了此通道,则尝试将此通道置于阻塞模式将导致抛出
				ssc.configureBlocking(false);

				ssc.socket().bind(new InetSocketAddress(11011));

				// 通过调用系统级默认 SelectorProvider 对象的 openSelector 方法来创建新的选择器。
				Selector selector = Selector.open();

				// 向给定的选择器注册此通道,返回一个选择键
				// SelectionKey selectionKey =
				ssc.register(selector, SelectionKey.OP_ACCEPT);

				// 轮询
				while (true) {
					// 选择一组键,其相应的通道已为 I/O 操作准备就绪。
					// 此方法是阻塞模式的选择操作。
					// 仅在至少选择一个通道、调用此选择器的 wakeup 方法,或者当前的线程已中断(以先到者为准)后此方法才返回。
					// 返回:
					// 已更新其准备就绪操作集的键的数目,该数目可能为零
					// 唤醒此方法的方法为wakeUp[selector.wakeup()];
					int readyChannels = selector.select();

					if (readyChannels == 0)
						continue;

					// 返回此选择器的已选择键集。
					// 可从已选择键集中移除键,但是无法直接添加键。
					// 试图向该键集中添加对象会导致抛出 UnsupportedOperationException。
					// 已选择键集是非线程安全的。
					Set<SelectionKey> selectedKey = selector.selectedKeys();

					for (Iterator<SelectionKey> iter = selectedKey.iterator(); iter.hasNext();) {
						SelectionKey key = iter.next();

						// 测试此键的通道是否已准备好接受新的套接字连接。
						if (key.isAcceptable()) {
							System.out.println("execute method::key.isAcceptable()");

							// 返回为之创建此键的通道。即使已取消该键,此方法仍继续返回通道。
							ServerSocketChannel server = (ServerSocketChannel) key.channel();

							// 接受到此通道套接字的连接。
							// 如果此通道处于非阻塞模式,那么在不存在挂起的连接时,此方法将直接返回 null。
							// 否则,在新的连接可用或者发生 I/O 错误之前会无限期地阻塞它。
							// 不管此通道的阻塞模式如何,此方法返回的套接字通道(如果有)将处于阻塞模式。
							SocketChannel client = server.accept();

							// 非阻塞
							client.configureBlocking(false);

							// 用于读取操作的操作集位。
							client.register(selector, SelectionKey.OP_READ);

							// 测试此键的通道是否已完成其套接字连接操作
						} else if (key.isConnectable()) {
							System.out.println("execute method::key.isConnectable()");

							// 测试此键的通道是否已准备好进行读取。
						} else if (key.isReadable()) {
							System.out.println("execute method::key.isReadable()");
							// 写出文件信息
							ByteBuffer bb = ByteBuffer.allocate(1024);
							SocketChannel sc = (SocketChannel) key.channel();
							sc.read(bb);
							bb.flip();
							while (bb.hasRemaining()) {
								System.out.print((char) bb.get());
							}
							System.out.println();

							// 将操作转移为写操作
							SocketChannel sch = (SocketChannel) key.channel();

							// 链式的配置注册写操作
							sch.configureBlocking(false).register(selector, SelectionKey.OP_WRITE);

							// 测试此键的通道是否已准备好进行写入。
						} else if (key.isWritable()) {
							System.out.println("execute method::key.isWritable()");

							SocketChannel sc = (SocketChannel) key.channel();
							sc.write(ByteBuffer.wrap("tomorrow is Saturday".getBytes()));

							selector.close();
							ssc.close();
							return;
						}

						// 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)
						iter.remove();
					}
				}
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}
	}

	private static class ChannelClient {
		public static void send() {
			try {
				// 打开通道
				SocketChannel channel = SocketChannel.open();
				// 非阻塞
				channel.configureBlocking(false);
				// 打开选择器
				Selector selector = Selector.open();
				// 为选择器注册通道
				channel.register(selector, SelectionKey.OP_CONNECT);
				// 建立连接
				channel.connect(new InetSocketAddress("127.0.0.1", 11011));

				// 轮询
				while (true) {
					selector.select();
					Set<SelectionKey> key = selector.selectedKeys();
					for (Iterator<SelectionKey> iter = key.iterator(); iter.hasNext();) {
						SelectionKey selectionKey = iter.next();

						// 测试此键的通道是否已完成其套接字连接操作
						if (selectionKey.isConnectable()) {

							// 连接建立事件,已成功连接至服务器
							channel = (SocketChannel) selectionKey.channel();

							// 判断此通道上是否正在进行连接操作
							if (channel.isConnectionPending()) {

								// 完成套接字通道的连接过程。
								// 如果已连接了此通道,则不阻塞此方法并且立即返回 true。如果此通道处于非阻塞模式,那么当连接过程尚未完成时,此方法将返回 false。
								// 如果此通道处于阻塞模式,则在连接完成或失败之前将阻塞此方法,并且总是返回 true 或抛出描述该失败的、经过检查的异常。
								// 可在任意时间调用此方法。如果正在调用此方法时在此通道上调用读取或写入操作,则在此调用完成前将首先阻塞该操作。
								// 如果试图发起连接但失败了,也就是说如果调用此方法导致抛出经过检查的异常,则关闭此通道。
								channel.finishConnect();

								System.out.println("connect success !");
								// 写出信息
								channel.write(ByteBuffer.wrap("worinixianrenbanban".getBytes("UTF-8")));

								// 注册读事件
								channel.register(selector, SelectionKey.OP_READ);
							}
						} else if (selectionKey.isReadable()) {
							System.out.println("sad!  >_<|");
							// 读出操作
							SocketChannel sc = (SocketChannel) selectionKey.channel();
							ByteBuffer bb = ByteBuffer.allocate(1024);
							sc.read(bb);
							bb.flip();
							while (bb.hasRemaining()) {
								System.out.print((char) bb.get());
							}

							// 关闭操作
							selector.close();
							channel.close();

							// 退出轮询方法
							return;
						}
					}
					// key.clear();
				}

			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}


例子二:展示SocketChannel和FileChannel的使用

Server:

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;


// ~ File Information
// ====================================================================================================================
//套接字通道 -->本地文件通道
public class UpLoadServer {

	public static void main(String[] args) {
		try {
//			开启服务
			ServerSocketChannel ssc = ServerSocketChannel.open();
//			bind端口号
			ssc.socket().bind(new InetSocketAddress(10234));
//			是否阻塞
			ssc.configureBlocking(true);
			while (true) {
				System.out.println("等待写入》》》》》》》》》》");
//				等待接收
				SocketChannel sc = ssc.accept();
				if (sc != null) {

//					写出文件(本地文件通道)
					FileChannel fc = new FileOutputStream(
							"C:\\Users\\Administrator\\Desktop\\BM_HOLIDAY_APPLY1.jpg").getChannel();
					int len = 0;
					
//					设置缓冲区容量
					ByteBuffer buffer = ByteBuffer.allocate(1024);
					
//					将网络通道内容写入此缓冲区
					while ((len = sc.read(buffer)) > 0) {
						
//						反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。
						buffer.flip();
					
//						从缓冲区写到本地文件	
						fc.write(buffer);
	
//						清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。 	
						buffer.clear();
					}
					sc.close();
					fc.close();
				}
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


Client:

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

// ~ File Information
// ====================================================================================================================
//本地文件通道-->套接字通道
public class UpLoadClient {
	
	public static void main(String[] args) {
		try {
			//获取连接
			SocketChannel sc = SocketChannel.open(new InetSocketAddress("localhost", 10234));
			//设置为阻塞
			sc.configureBlocking(true);
			//拿到文件通道
			FileChannel fc= new FileInputStream(
					"E:\\文本+图片\\joker.jpg")
						.getChannel();
//			设置缓冲区大小
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			int ind;
			
//			public abstract int read(ByteBuffer dst)  throws IOException			
//			将字节序列从此通道中读入给定的缓冲区。
			while((ind=fc.read(buffer))>0){
				
//				反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。
				buffer.flip();
				
//				public abstract int write(ByteBuffer src) throws IOException 从接口 WritableByteChannel 复制的描述 
//				将字节序列从给定的缓冲区中写入此通道。 
				sc.write(buffer);
				
//				public final Buffer clear()
//				清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。 	
				buffer.clear();
			}
			sc.close();
			fc.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


以上两个例子简单介绍了NIO中代码的具体使用,如果想深入了解,请查看JDK帮助手册

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值