nio基础学习(一)

nio是java New IO的简称,在jdk1.4里提供的新api.
Sun官方标榜的特性如下:
1.为所有的原始类型提供(Buffer)缓存支持.
2.字符集编码解码解决方案。
3.一个新的原始I/O抽象Channel.
4.支持锁和内存映射文件的文件访问接口。
5.提供多路(non-bloking)非阻塞式的高伸缩性网络I/O.
NIO真的象官方说的这么强大吗?首先看下面的这个实例:普通IO(带缓冲区的)读写文件和NIO读写文件的性能对比.

直接看代码:IoMainTest和NioMainTest.

<span style="font-size:12px;"><span style="font-size:12px;">package com.ilucky.nio.one.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author IluckySi
 * @date 20140618
 */
public class IoMainTest {

	public static void main(String[] args) {
	    File fileRead = new File("D:/fileRead.txt");  
	    File fileWrite = new File("D:/fileIOWrite.txt");  
	    FileInputStream fis = null;
	    BufferedInputStream bis = null;
	    FileOutputStream fos = null;
	    BufferedOutputStream bos = null;
	    try {  
	    	fis = new FileInputStream(fileRead);
	    	bis = new BufferedInputStream(fis);
	        fos = new FileOutputStream(fileWrite);  
	        bos = new BufferedOutputStream(fos);
	        byte[] buffer=new byte[10240];  
	        int length = -1;
	        long start =  System.currentTimeMillis();
	        //length = bis.read(buffer)意思是读取10KB字节放入缓存并返回读到字节的长度.
	        while((length = bis.read(buffer))!=-1){  
	            bos.write(buffer, 0, length);
	            bos.flush();
	        }  
	        long end = System.currentTimeMillis();
	        System.out.println("本次IO操作耗时: " + (end - start) + "毫秒!");
	    } catch (FileNotFoundException e) {  
	        e.printStackTrace();  
	    } catch (IOException e) {  
	        e.printStackTrace();  
	    }  finally {
    		try {
    			if(bos != null) {
					bos.close();
					bos =null;
    			}
    			if(fos != null) {
    				fos.close();
    				fos = null;
    			}
    			if(bis != null) {
    				bis.close();
    				bis = null;
    			}
    			if(fis != null) {
    				fis.close();
    				fis = null;
    			}
			} catch (IOException e) {
				e.printStackTrace();
			}
    	}
	}
}
/**
输出结果(注意文件大小是291MB, 每次读取10KB, 共输出5次结果):
本次IO操作耗时: 940毫秒!
本次IO操作耗时: 861毫秒!
本次IO操作耗时: 925毫秒!
本次IO操作耗时: 904毫秒!
本次IO操作耗时: 962毫秒!
 */
/**
输出结果(注意文件大小是1.02G, 每次读取10KB, 共输出3次结果):
本次IO操作耗时: 34880毫秒!
本次IO操作耗时: 29267毫秒!
本次IO操作耗时: 31435毫秒!
 */
/**
 io分析:
1.首先,正确理解byte[] buffer=new byte[10240]; 这相当于一个缓冲区, 即每次读取10KB放到缓存中,
   因为java中输入输出流类都是按单字节的读写方式进行IO操作的,也就是说每次读写一个字节的数据,
   这样很大程度上影响了系统的性能,因此,java又专门提供了高IO效率的缓冲类(这里是通过装饰者模式添加的),
   每次读写一个缓冲区的数据,这样很大程度上提高了系统的性能.
2.其次,write(byte[] b, int offset, int length)中的offset是指字节数组的偏移量.
   在网上的一些资料中经常看到这样的写法write(buffer),这样写是有问题的,
   经过多次测试,这么写每次write后的文件都比源文件大一些,究其原因,是因为读到最后一次时如果不够10KB,
   他也会按10KB写到目的文件中.
3.然后是flush方法,flush是把缓冲区中的内容全部写到文件上,如果没有他,可能有很多内容还存在于缓存中,即内容还有丢失的可能.
4.最后是close方法,close是文件完成真正的标志,通过调用close方法,来表示目的文件是一个完整的文件.
 */
</span></span>
<span style="font-size:12px;"><span style="font-size:12px;">package com.ilucky.nio.one.nio;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @author IluckySi
 * @date 20140618
 */
public class NioMainTest {

	public static void main(String[] args) {
	    File fileRead = new File("D:/fileRead.txt");  
	    File fileWrite = new File("D:/fileIOWrite.txt");  
	    FileInputStream fis = null;
	    FileChannel fic = null;
	    FileOutputStream fos = null;
	    FileChannel foc = null;
	    try {  
	    	fis = new FileInputStream(fileRead);
	    	fic = fis.getChannel();
	        fos = new FileOutputStream(fileWrite);  
	    	foc = fos.getChannel();
	        ByteBuffer byteBuffer = ByteBuffer.allocate(10240);
	        long start =  System.currentTimeMillis();
	        //把通道的数据读入缓冲区.
	        while(fic.read(byteBuffer) != -1) {
            	byteBuffer.flip();  
            	//将缓冲区的数据写入另一个通道.
            	foc.write(byteBuffer);  
                byteBuffer.clear();  
            }
	        long end = System.currentTimeMillis();
	        System.out.println("本次IO操作耗时: " + (end - start) + "毫秒!");
	    } catch (FileNotFoundException e) {  
	        e.printStackTrace();  
	    } catch (IOException e) {  
	        e.printStackTrace();  
	    }  finally {
    		try {
    			if(foc != null) {
    				foc.close();
    				foc =null;
    			}
    			if(fos != null) {
    				fos.close();
    				fos = null;
    			}
    			if(fic != null) {
    				fic.close();
    				fic = null;
    			}
    			if(fis != null) {
    				fis.close();
    				fis = null;
    			}
			} catch (IOException e) {
				e.printStackTrace();
			}
    	}
	}
}

/**
输出结果(注意文件大小是291MB, 每次读取10KB, 共输出5次结果):
本次IO操作耗时: 910毫秒!
本次IO操作耗时: 1163毫秒!
本次IO操作耗时: 937毫秒!
本次IO操作耗时: 997毫秒!
本次IO操作耗时: 900毫秒!
 */
/**
输出结果(注意文件大小是1.02G, 每次读取10KB, 共输出3次结果):
本次IO操作耗时: 37308毫秒!
本次IO操作耗时: 31083毫秒!
本次IO操作耗时: 35427毫秒!
*/

</span></span>
通过代码我们可以看出针对简单的读写操作NIO并不占优势,基本上和普通IO(带缓冲区的)读写文件性能不相上下,
甚至还稍微低一些,那么NIO的优势在哪里呢?下面首先通过一个实例来展示一下基于NIO的多路非阻塞网络服务器.

1.创建服务器,直接看代码NioServer.

<span style="font-size:12px;"><span style="font-size:12px;">package com.ilucky.nio.two.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/**
 * @author IluckySi
 * @date 20140618
 */
public class NioServer {
	
	private Selector selector;
	
	private ServerSocketChannel serverSocketChannel;

	public static void main(String[] args) throws IOException {
		//创建服务器.
		NioServer server = new NioServer(9911);
		//启动服务器.
		server.start();
	}
	
	public NioServer(int port) throws IOException {
		//创建选择器.
		selector = Selector.open();
		//创建通道.
		serverSocketChannel = ServerSocketChannel.open();
		//创建服务器.
		ServerSocket serverSocket = serverSocketChannel.socket();
		//为服务器绑定监听端口.
		serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), port));
		//设置通道为非阻塞模式.
		serverSocketChannel.configureBlocking(false);
		//为通道注册选择器,并设置其支持Accept操作.
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
	}

	public void start() {
		System.out.println("Nio Server启动成功!");
		try {
			while (true) {
				//当通道上有数据到来时继续执行,否则会一直阻塞.
				selector.select();
				//遍历所有注册过选择器的通道.
				Set<SelectionKey> selectionKeys = selector.selectedKeys();
				Iterator<SelectionKey> iterator = selectionKeys.iterator();
				while (iterator.hasNext()) {
					SelectionKey selectionKey = (SelectionKey) iterator.next();
					//删除SelectionKey,防止重复处理.
					iterator.remove();
					//分发SelectionKey,进行业务处理.
					dispatch(selectionKey);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void dispatch(SelectionKey selectionKey) {
		//判断SelectionKey对应的通道是否支持ACCEPT操作.
		if (selectionKey.isAcceptable()) {
			try {
				//获取客户端连接的通道.
				ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
				SocketChannel socketChannel = (SocketChannel) serverSocketChannel.accept();
				//设置通道为非阻塞模式.
				socketChannel.configureBlocking(false);
				//在客户端连接成功后,为了可以接收到客户端发送过来的消息,需要为通道注册读权限.
				socketChannel.register(selector, SelectionKey.OP_READ);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} 
		//判断SelectionKey对应的通道是否支持READ操作.
		if (selectionKey.isReadable()) {
			try {
				SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
				ByteBuffer buffer = ByteBuffer.allocate(1024);
				int bytesRead = socketChannel.read(buffer);
				if (bytesRead > 0) {
					buffer.flip();
					//将ByteBuffer转化为为UTF-16编码的字符串.
					String message = Charset.forName("UTF-16").newDecoder().decode(buffer).toString();
					System.out.println("接收到来自客户端"+ socketChannel.socket().getRemoteSocketAddress() + "的消息: "+ message);
					//将字符串转化为为UTF-16编码的ByteBuffer.
					buffer = ByteBuffer.wrap("success".getBytes("UTF-16"));
					//向客户端写回消息.
					socketChannel.write(buffer);
				}
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				//操作结束后取消操作.
				selectionKey.cancel();
				if (selectionKey.channel() != null) {
					try {
						selectionKey.channel().close();
					} catch (IOException e1) {
						e1.printStackTrace();
					}
				}
			}
		}
	}
}
</span></span>

2.创建客户端,直接看代码NioClientOne和NioClientTwo(两个文件内容一样).

<span style="font-size:12px;"><span style="font-size:12px;">package com.ilucky.nio.two.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/**
 * @author IluckySi
 * @date 20140618
 */
public class NioClientOne {

	private Selector selector;

	private SocketChannel socketChannel;

	public static void main(String[] args) throws IOException {
		//模拟业务: 隔5秒钟向服务器发送一条消息.
		for (int i = 0; i < 5; i++) {
			NioClientOne client = new NioClientOne(9911);
			client.sendMessage("Hello! Nio Server, I am NioClientOne!");
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public NioClientOne(int port) throws IOException {
		//创建选择器.
		selector = Selector.open();
		//创建通道.
		socketChannel = SocketChannel.open(new InetSocketAddress(InetAddress.getLocalHost(), port));
		//设置通道为非阻塞模式.
		socketChannel.configureBlocking(false);
		//为通道注册选择器,并设置其支持READ操作
		socketChannel.register(selector, SelectionKey.OP_READ);
		//启动读取线程.
		new Thread(new NioClientReadThread()).start();
	}

	public void sendMessage(String message) throws IOException {
		//将字符串转化为为UTF-16编码的ByteBuffer.
		ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes("UTF-16"));
		//向服务器写消息.
		socketChannel.write(writeBuffer);
	}

	class NioClientReadThread implements Runnable {
		public void run() {
			try {
				while (true) {
					//当通道上有数据到来时继续执行,否则会一直阻塞.
					selector.select();
					//遍历所有注册过选择器的通道.
					Set<SelectionKey> selectionKeys = selector.selectedKeys();
					Iterator<SelectionKey> iterator = selectionKeys.iterator();
					while (iterator.hasNext()) {
						SelectionKey selectionKey = (SelectionKey)iterator.next();
						//删除SelectionKey,防止重复处理.
						selector.selectedKeys().remove(selectionKey);
						//分发SelectionKey,进行业务处理.
						dispatch(selectionKey);
					}
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		
		public void dispatch(SelectionKey selectionKey) {
			//判断SelectionKey对应的通道是否支持READ操作.
			if (selectionKey.isReadable()) {
				try {
					SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
					ByteBuffer buffer = ByteBuffer.allocate(1024);
					socketChannel.read(buffer);
					buffer.flip();
					//将ByteBuffer转化为为UTF-16编码的字符串.
					String message = Charset.forName("UTF-16").newDecoder().decode(buffer).toString();
					System.out.println("接收到来自服务器" + socketChannel.socket().getRemoteSocketAddress() + "的消息: "+ message);
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					//操作结束后取消操作.
					selectionKey.cancel();
					if (selectionKey.channel() != null) {
						try {
							selectionKey.channel().close();
						} catch (IOException e1) {
							e1.printStackTrace();
						}
					}
				}
			}
		}
	}
}</span></span>
看完了基于NIO的多路非阻塞网络服务器,再回顾一下阻塞服务器.

1.创建服务器,直接看代码IoServer.

<span style="font-size:12px;"><span style="font-size:12px;">package com.ilucky.nio.two.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author IluckySi
 * @date 20140618
 */
public class IoServer {

	public static void main(String[] args) {
		try {
			ServerSocket serverSocket = new ServerSocket(9911);
			System.out.println("Io Server启动成功!");
			while (true) {
				Socket socket = null;
				PrintWriter writer = null;
				BufferedReader reader = null;
				try {
					// 如果没有消息阻塞在这里.
					socket = serverSocket.accept();
					writer = new PrintWriter(socket.getOutputStream());
					reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
					System.out.println("接收到来自客户端"+ socket.getRemoteSocketAddress() + "的消息: "+ reader.readLine());
					writer.println("success!");
					writer.flush();
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					if(reader != null) {
						reader.close();
						reader = null;
					}
					if(writer != null) {
						writer.close();
						writer = null;
					}
					if(socket != null) {
						socket.close();
						socket = null;
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
</span></span>

2.创建客户端,直接看代码IoClientOne和IoClientTwo.

<span style="font-size:12px;"><span style="font-size:12px;">package com.ilucky.nio.two.io;

import java.io.BufferedReader;
import java.io.InputStreamReader;  
import java.io.PrintWriter;  
import java.net.Socket;  

/**
 * @author IluckySi
 * @date 20140618
 */
public class IoClientOne {

    public static void main(String[] args) throws Exception {  
    		//模拟业务: 隔5秒钟向服务器发送一条消息.
    	for(int i = 0; i < 5; i++) {
	        Socket client = new Socket("127.0.0.1", 9911);  
	        PrintWriter writer = new PrintWriter(client.getOutputStream());  
	        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));  
	        writer.println("Hello! Io Server, I am IoClientOne!");  
	        writer.flush();
	        System.out.println("接收到来自服务器" + client.getRemoteSocketAddress() + "的消息: "+ reader.readLine());
	        writer.close();
	        reader.close();
	        client.close();  
	        Thread.sleep(5000);
    	}
    }  
}
</span></span>

通过上面的两个小例子我们对nio有了更深的认识.

nio服务器和传统服务器的对比:

传统的java网络编程中都是在服务端创建一个ServerSocket,然后为每一个客户端单独创建一个线程处理请求,
由于对于CPU而言,线程的开销是很重要的,无限创建线程会让操作系统崩溃,因此,比较好的方法是在系统启动的时候创建一个动态的线程池,
例如Tomcat就是采用这种解决方案,然而,这种解决方案在高并发的情况下也不是很乐观,当线程池大小超过CPU瓶颈的时候,
速度就明显降低了.然而有了nio以后,服务端无需再创建多个线程,也无需创建线程池,仅仅只需要1个线程即可以处理全部客户端请求,
通过这一个线程nio服务器将每一个客户端连接请求注册到特定的Selector对象上,这就可以在单线程中利用Selector对象管理大量
并发的网络连接,更好的利用了系统资源.
nio通信过程:
当有读或写等任何注册的事件发生时,可以从Selector中获得相应的SelectionKey,从SelectionKey中可以找到发生的事件和该事件
所发生的具体的SelectableChannel,以获得客户端发送过来的数据/
由于在非阻塞网络I/O中采用了事件触发机制,处理程序可以得到系统的主动通知,从而可以实现底层网络I/O无阻塞,流畅地读写,
而不像在原来的阻塞模式下处理程序需要不断循环等待.使用NIO,可以编写出性能更好,更易扩展的并发型服务器程序.
nio的设计原理:
设计原理有点像设计模式中的观察者模式,由Selector去轮流咨询各个SocketChannel通道是否有事件发生,如果有,则选择出所有的Key
集合,然后传递给处理程序.我们通过每个key就可以获取客户端的SocketChannel,从而进行通信.
如果Selector发现所有通道都没有事件发生,则线程进入睡眠状态Sleep,即阻塞,等到客户端有事件发生时会自动唤醒选择器selector.
知识补充:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值