JAVA NIO與IO簡單對比

NIO和IO

NIO的四個關鍵數據類型

  1. Buffer:它包含數據且用於讀寫的線性表結構,還提供一個特殊類用於內存映射的I/O操做。
  2. Charset:提供Unicode字符串映射到字節序列以及逆映射的操做。
  3. Channels:包含socket,file和pip三種,是一種雙向交通的通道。
  4. Selectors:將多元異步I/O操做集中到一個或多個線程中(相似於linux的select函數)

傳統IO

服務端:java

ServerSocket server = new ServerSocket(1000);
Socket conn = server.accept();
InputStream in = conn.getInputStream();
InputStreamReader reader = new BufferedReader(reader);
Request request = new Request();
while(!request.isComplete()){
	String line = reader.readLine();
	request.addLine(line);
}

上述操做有兩個問題:linux

  1. BufferedReader類的readLine() 方法在其緩衝未滿時,會一直阻塞,只有必定的數據填滿緩衝或者client關閉鏈接,此方法才能返回。
  2. BufferedReader會產生大量的垃圾須要GC。BufferedReader須要建立緩衝區來從client讀取數據,可是一樣建立了一些字符串存儲這些數據。

BufferedReader默認有$2^{13}$次方字符的緩衝大小。數組

相似的,在處理寫操做時,一次寫入一個字符,效率很低,也須要使用緩衝寫,可是這也會插死你橫更多的垃圾。服務器

在傳統的I/O中要使用大量的線程,一般使用線程池實現來處理請求。 可是即便這樣,仍是會有不少時間阻塞在I/O上,沒有有效的利用CPU網絡

NIO

Buffer

傳統的I/O使用String來操做,浪費資源,新I/O經過使用Buffer讀寫數據避免浪費。app

Buffer對象是線性的,有序的數據集合,他根據其類別只包含惟一的數據類型。異步

  • java.nio.Buffer: 類描述
  • java.nio.ByteBuffer:字節類型。能夠從ReadableByteChannel中讀,在WritableByteChannel中寫
  • java.nio.CharBuffer:字符類型,不能寫入通道
  • java.nio.DoubleBuffer:double類型,不能寫入通道
  • java.nio.FloatBuffer:float類型
  • java.nio.IntBuffer:int類型
  • java.nio.LongBuffer:long類型
  • java.nio.ShortBuffer:short類型

能夠使用allocate(int capacity)方法或者allocateDirect(int capacity) 方法分配一個Buffer。socket

特別的,能夠經過調用FileChannel.map(int mode, long position, int size)建立MappedByteBufferide

Direct Buffer 在內存中分配一段連續的塊並使用本地訪問方法讀寫數據。non direct Buffer用過java中的數組讀寫數據。函數

有時間必須使用非直接的緩衝,例如使用任何wrap方法(如ButeBuffer.wrap(byte[]))在java數據自出上建立buffer。

字符編碼

ByteBuffer中存放數據涉及兩個問題:字節的順序和字符轉換。ByteBuffer內部經過ByteOrder類處理了字節順序問題,可是並未解決字符轉換的問題。ByteBuffer沒有提供方法讀寫String。

java.nio.charset.Charset處理字符轉換的問題。經過構造CharsetEncoder和CharsetDecoder將字符序列轉爲字節和逆轉換。

通道

java.io類中沒有一個類能夠讀寫Buffer類型,nio提供Channel讀寫Buffer。channel能夠認爲是一種鏈接,能夠使到特定的設備,程序或者是網絡。 channel類的等級結構以下: Channel(interface)->ReadableByteChannel(interface)->ScatteringByteChannel(interface) Channel(interface)->WritableByteChannel(interface)->GatherByteChannel(interface)

ByteChannel(interface)繼承自: ReadableByteChannel(interface) WritableByteChannel(interface)

GatherByteChannel能夠一次將多個Buffer中的數據寫入通道,相反的ScatteringByteChannel能夠一次將數據從通道中讀入多個buffer中。還能夠設置通道使其爲阻塞或非阻塞I/O操做服務。

爲了使通道與傳統I/O兼容,Channel提供了靜態的Stream或Reader。

Selector

在過去的阻塞I/O中,咱們通常知道何時能夠向stream中讀或寫,由於方法調用直到stream準備好時返回。可是使用非阻塞通道,咱們須要一些方法來知道何時通道準備好了。在NIO包中,設計Selector就是爲了這個目的。

SelectableChannel能夠註冊特定的事件,而不是在事件發生時通知應用,通道跟蹤事件。而後,當應用調用Selector上的任意一個selection方法時,它查看註冊了的通道看是否有任何感興趣的事件發生。

selector工做流程

並非全部的通道都支持全部的操做。SelectionKey類定義了全部可能的操做位,將要用兩次。

  1. 當應用調用SelectableChannel.register(Selector sel,int op)方法註冊通道時,它將所需操做做爲第二個參數傳遞到方法中。
  2. 一旦SelectionKey被選中了,SelectionKeyreadyOps()方法返回全部通道支持操做的位數的和。SelectableChannelvalidOps方法返回每一個通道容許的操做。

註冊通道不支持的操做將引起IllegalArgumentException異常.

SelectableChannel子類支持的操做:

ServerSocketChannel OP_ACCEPT 
SocketChannel OP_CONNECT, OP_READ, OP_WRITE 
DatagramChannel OP_READ, OP_WRITE 
Pipe.SourceChannel OP_READ 
Pipe.SinkChannel OP_WRITE

例子

  1. 簡單網頁內容下載
  2. 簡單加法服務器和客戶端
  3. 非阻塞加法服務器

簡單網頁下載

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

public class WebDownload {

	private final static Charset charset = Charset.forName("UTF-8");
	private SocketChannel clientChannel;

	public void download() {
		connect();
		sendRequest();
		readResponse();
	}

	//發送GET請求到CSDN的文檔中心
	private void sendRequest() {
		//使用channel.write方法,它須要CharByte類型的參數,使用
		//Charset.encode(String)方法轉換字符串。
		try {
			clientChannel.write(charset.encode("GET / HTTP/1.1\r\n\r\n"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void readResponse(){
		ByteBuffer buff = ByteBuffer.allocate(1024);//建立1024字節的緩衝
		
		try {
			// -1 if the channel has reached end-of-stream
			while(clientChannel.read(buff)!=-1){
				buff.flip();//flip方法在讀緩衝區字節操做以前調用。
				
				System.out.println(charset.decode(buff));
				
				buff.clear();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private boolean connect() {
		InetSocketAddress socketAddr = new InetSocketAddress("www.baidu.com", 80);
		try {
			clientChannel = SocketChannel.open();
			clientChannel.connect(socketAddr);
			return true;
		} catch (IOException e) {
			e.printStackTrace();
		}

		return false;
	}

	public static void main(String[] args) {
		new WebDownload().download();
	}

}

簡單加法服務器和客戶端

server端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;


public class AddServer {
	
	private ServerSocketChannel server = null;
	private SocketChannel client = null;
	private ByteBuffer buff = ByteBuffer.allocate(8);
	private IntBuffer intBuff = buff.asIntBuffer();
	
	public void connect(){
		try {
			server = ServerSocketChannel.open();
			server.bind(new InetSocketAddress(80));
			System.out.println("channel open!");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void waitForConnection(){
		try {
			client = server.accept();
			if(client!=null){
				System.out.println("client connect!");
				processRequest();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void  processRequest(){
		buff.clear();
		try {
			client.read(buff);
			int result = intBuff.get(0)+intBuff.get(1);
			buff.flip();
			buff.clear();
			intBuff.put(0, result);
			client.write(buff);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	public void run(){
		this.connect();
		this.waitForConnection();
		this.processRequest();
	}
	/**
	 * [@param](http://my.oschina.net/u/2303379) args
	 */
	public static void main(String[] args) {
		new AddServer().run();
	}

}

client 端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.SocketChannel;

public class AddClient {
	private SocketChannel client = null;
	private ByteBuffer buff = ByteBuffer.allocate(8);
	private IntBuffer intBuff = buff.asIntBuffer();

	public void connect() {
		try {
			client = SocketChannel.open();
			client.connect(new InetSocketAddress("localhost", 80));
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public void request(int a, int b) {
		buff.clear();
		intBuff.put(0, a);
		intBuff.put(1, b);
		try {
			client.write(buff);
			System.out.println("send request :" + a + "+" + b);
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public int getresult() {
		buff.clear();
		int result = 0;
		try {
			client.read(buff);
			result = buff.getInt(0);
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally{
			try {
				client.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return result;

	}
	
	public int start(int a, int b){
		this.connect();
		this.request(a, b);
		return this.getresult();
	}

	/**
	 * [@param](http://my.oschina.net/u/2303379) args
	 */
	public static void main(String[] args) {
		System.out.println(new AddClient().start(123, 345));
	}

}

非阻塞加法服務器

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;


public class AddServer {
	
	private ServerSocketChannel server = null;
	private SocketChannel client = null;
	private ByteBuffer buff = ByteBuffer.allocate(8);
	private IntBuffer intBuff = buff.asIntBuffer();
	
	public void connect(){
		try {
			server = ServerSocketChannel.open();
			server.bind(new InetSocketAddress(80));
			server.configureBlocking(false);
			System.out.println("channel open!");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void waitForConnection(){
		Selector acceptSelector;
		try {
			acceptSelector = SelectorProvider.provider().openSelector();
			SelectionKey acceptKey = server.register(acceptSelector, SelectionKey.OP_ACCEPT);
			int keyadded = 0; 
			
			while( (keyadded = acceptSelector.select()) > 0){
				Set readyKeys = acceptSelector.selectedKeys();
				Iterator it = readyKeys.iterator();
				
				while(it.hasNext()){
					SelectionKey sk = (SelectionKey)it.next();
					it.remove();
					ServerSocketChannel nextReaedy = (ServerSocketChannel)sk.channel(); 
					client = nextReaedy.accept();
					processRequest();
				}
			}

		} catch (IOException e1) {
			e1.printStackTrace();
		}
		
	}
	
	public void  processRequest(){
		buff.clear();
		try {
			client.read(buff);
			int result = intBuff.get(0)+intBuff.get(1);
			buff.flip();
			buff.clear();
			intBuff.put(0, result);
			client.write(buff);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	public void run(){
		this.connect();
		this.waitForConnection();
		this.processRequest();
	}
	/**
	 * [@param](http://my.oschina.net/u/2303379) args
	 */
	public static void main(String[] args) {
		new AddServer().run();
	}

}

非阻塞的加法服務器首先經過SelectorProvider工廠方法創建選擇器

acceptSelector = SelectorProvider.provider().openSelector();

而後在ServerSocketChannel上註冊選擇器和對應的事件。

SelectionKey acceptKey = server.register(acceptSelector, SelectionKey.OP_ACCEPT);

經過選擇器獲取當前是否有client鏈接到server:

acceptSelector.select()>0

而後將有client連接到server,獲取成功鏈接的迭代器。遍歷已經準備好的鏈接,分別處理請求。

相關文章
相關標籤/搜索