java的NIO概述

       概述html

 java中io輸入輸出流是阻塞的,如BufferedReader的readLine(),InputStrean的read()方法。當程序沒有讀到有效數據,程序將在此處阻塞。比較典型的是,在用socket傳遞數據,若是發送數據的一方,沒有關閉流,接收數據的一方,接收數據後將一直阻塞。面向流的輸入輸出,最底層一次只能處理一個字節,所以效率不高。java

    jdk1.4提供了新的IO即NIO。數組

    NIO中有三個重要的特性。
安全

    Channel,通道。模擬傳統的輸入輸出流,既能夠作輸入又能夠作輸出。
服務器

    Buffer,緩衝。本質是一個數組,發送到Channel的全部對象都必須先放到Buffer中。讀取數據也先讀到Buffer。
多線程

    Selector。selector容許單線程處理多個Channel,可監控多個服務socket

    Buffer簡介
ide

    buffer中有三個重要概念:容量(capactiy)、界限(limit)和位置(position,帶讀取數據的下標)。
ui

    這些值知足以下關係:0<=mark<=position<=limit<=capacity
spa

    Buffer中包含兩個重要的方法flip和clear,flip爲從Buffer中取出數據作準備,clear則向Buffer中裝入數據作準備。

    當Buffer中裝入數據結束後,調用flip方法,該方法將limit設置爲position所在的位置,將position設爲0,這樣Buffer中讀數據老是從0開始,作好輸出準備。當Buffer輸出數據結束,Buffer調用clear方法,clear方法不是清空Buffer的數據,它僅僅將poition置爲0,將limit置爲capacity,這樣爲再次向Buffer裝入數據作準備,此時原先的數據沒有清除,新寫的數據會替換原先的數據。

    Channel簡介

    全部的Channel都不該該經過構造器來直接建立,而是經過InputStream、OutputStream的getChannel方法來返回對應的Channel。

    Channel中最經常使用的三類方法是map、read和write,其中map方法用於將Channel對應的部分或所有數據映射爲ByteBuffer;而read或write方法用於向Buffer讀取寫入數據。

static void channelTest() throws Exception{
		FileInputStream is = new FileInputStream(new File("G:/百無聊賴.txt"));
		FileOutputStream out = new FileOutputStream(new File("G:/ttt.txt"));
		FileChannel readChannel = is.getChannel();
		FileChannel writechannel = out.getChannel();
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		while(readChannel.read(buffer)!=-1){
			buffer.flip();//準備取出數據
			writechannel.write(buffer);
			buffer.clear();//準備裝入數據
		}
		writechannel.close();
		readChannel.close();
		out.close();
		is.close();
	}

由於jdk對以前的IO採用nio的機制從新實現了,因此直接使用nio對數據進行讀寫並不經常使用。使用channel的map方法,進行內存映射,能夠快速的讀寫數據,可是這種方式不安全,僅使用與讀數據。NIO最經常使用的功能應該是它的Selector。

Java NIO的選擇器容許一個單獨的線程來監視多個輸入通道,你能夠註冊多個通道使用一個選擇器,而後使用一個單獨的線程來「選擇」通道:這些通道里已經有能夠處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。 

以下兩篇博文對NIO包括selector的介紹都不錯

http://www.iteye.com/magazines/132-Java-NIO#579

http://www.xuebuyuan.com/1600515.html 

下面構建一個小例子對比下NIO和IO服務器

服務端代碼:

package com.base.nio;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
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;

public class Server {

	public static void main(String[] args) throws Exception{
//		nioServer();
		socketServer();
	}
	public static void nioServer() throws Exception{
		int port=9999;
		Selector selector=Selector.open();
		ServerSocketChannel ssc=ServerSocketChannel.open();
		ServerSocket serverSocket=ssc.socket();
		serverSocket.bind(new InetSocketAddress(port));
		System.out.println("Server listen on port: "+port);
		ssc.configureBlocking(false);
		ssc.register(selector, SelectionKey.OP_ACCEPT);
		while(true){
			int nKeys=selector.select();
			if(nKeys>0){
				for (SelectionKey key : selector.selectedKeys()) {
					if(key.isAcceptable()){
						ServerSocketChannel server=(ServerSocketChannel) key.channel();
						SocketChannel sc=server.accept();
						if(sc==null){
							continue;
						}
						sc.configureBlocking(false);
						sc.register(selector, SelectionKey.OP_READ);
					}
					else if(key.isReadable()){
						ByteBuffer buffer=ByteBuffer.allocate(1024);
						SocketChannel sc=(SocketChannel) key.channel();
						int readBytes=0;
						readBytes = sc.read(buffer);//若是客戶端傳來的數據長度超過1024,會丟失報文。
						buffer.flip();
						if(readBytes>0){
							String message =Charset.forName("UTF-8").decode(buffer).toString();
							System.out.println("Message from client: "+ message);
							if("quit".equalsIgnoreCase(message.trim())){
								sc.close();
								selector.close();
								System.out.println("Server has been shutdown!");
								System.exit(0);
							}
							String outMessage="Server response:"+message;
							sc.write(Charset.forName("UTF-8").encode(outMessage));
						}
					}
				}
				selector.selectedKeys().clear();
			}
		}
	}

	public static void socketServer() throws IOException, InterruptedException, Exception {
		ServerSocket ss = new ServerSocket(9999);
		while(true){
			Socket socket = ss.accept();
			BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String getMsg = br.readLine();
			if("quit".equalsIgnoreCase(getMsg.trim())){
				ss.close();
				System.out.println("Server has been shutdown!");
				System.exit(0);
			}
			System.out.println(getMsg);
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			bw.write("Hello Client");
			bw.newLine();
			bw.flush();
			Thread.sleep(10000);
			Client.close(socket, null, bw, br);
		}
	}

}

客戶端代碼:

package com.base.nio;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class Client implements Runnable{

	public static void main(String[] args) throws Exception{
		for(int i=0;i<10;i++){
			Client c = new Client();
			Thread thread = new Thread(c);
			thread.start();
		}
	}
	@Override
	public void run() {
		Socket socket = null;
		OutputStream out = null;
		BufferedWriter bw = null;
		BufferedReader br = null;
		try {
			socket = new Socket("127.0.0.1", 9999);
			out = socket.getOutputStream();
			bw = new BufferedWriter(new OutputStreamWriter(out));
			bw.write("Hello Server");
			bw.newLine();
			bw.flush();
			Thread.sleep(10000);
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String retMsg = br.readLine();
			System.out.println(retMsg+Thread.currentThread().getName());
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try {
				Client.close(socket, out, bw,br);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void close(Socket socket, OutputStream out, BufferedWriter bw,BufferedReader br) throws Exception{
		if(br!=null)
			br.close();
		if(bw!=null)
			bw.close();
		if(out!=null)
			out.close();
		if(socket!=null)
			socket.close();
	}
}

經過運行分析,發現nio是非阻塞的,不用等每個客戶端的鏈接請求完成,就能夠進行下一個鏈接。而傳統的socket會等待客戶端的每一個請求完成,纔去鏈接下個客戶端請求。

nio是經過while(true)進行輪詢,來不斷監聽客戶端的請求狀態。而socket是經過阻塞的方式來等待客戶端的請求狀態。

感受NIO最大的優點就在於它的非阻塞,減小了等待客戶的響應。若是使用socket要想達到一樣的效果,必須使用線程池,使用線程池就相對耗資源些。

至於監聽多個chanel,也就是同時監聽多個端口。好比以上例子,9999用於監聽客戶端會話。若是服務端還要一個8888端口,來控制其餘服務,好比關閉服務等等。傳統的socket也必須用多線程來實現。

可是感受NIO採用這種輪的方式,不是很優雅。可能我瞭解的很少吧。

相關文章
相關標籤/搜索