java的NIO

java的NIO

        java的NIO主要有3個特性Channel、buffer、selector來保證I/O高可複用性,其中最重要的是buffer和selector操做。詳細教材查看 jakob jenkov教材:http://tutorials.jenkov.com/java-nio/index.html html

一、channel和buffer

           a、 channel:有點像流的管道,NIO從channel裏面獲取、發送數據。java的I/O已經從底層被NIO實現了一次,因此性能上和純粹的NIO中使用channel沒有太大的區別。
java

        channel的類型主要就下面幾種:
服務器

FileChannel            //文件
DatagramChannel        //UDP
SocketChannel          //socket
ServerSocketChannel    //socket服務

           b、 Buffer:就是在內存開闢的空間,用來臨時存放數據。這裏的buffer是以bytebuffer爲父類實現的HeapByteBuffer。
多線程

它有2個特色:一、讀寫控制、二、按着單個byte位來操做。dom

            一個Buffer有3箇中有的參數:jvm

                position:當前位置socket

                            一、寫:從當前能夠寫的地址開始(第一次從0開始),隨着寫入的增大。寫的時候最大爲capacity-1。性能

                            二、讀:從0開始、隨着讀開始移動增大。最大讀取到limitspa

                limit:寫模式下爲capacity。當轉換爲讀模式,則limit=position(寫入的個數)線程

                capacity:一個buffer的固定大小。

        以下示意圖

    例子:

#有一個Buffer是70字節
一、buffer.allocate(70):capacity=70、position=0、limit=70
一、寫如40個字節:capacity=70、position=40、limit=70
二、寫轉化爲讀:buffer.flip();capacity=70,position=0,limit=40
三、讀30字節:capacity=70、position=30、limit=40
四、讀轉寫:
       buffer.clear():全部剩餘數據都清空。capacity=70,position=0,limit=70
       buffer.compact():將剩餘的全部數據複製到buffer起始。capacity=70,position=10,limit=70

   一個fileChannel的例子

public static void main(String[] args) {
		try {
			RandomAccessFile aFile = new RandomAccessFile("D:/project/test/nio/1.txt", "rw");
			FileChannel fileChannel = aFile.getChannel();
			// buffer
			ByteBuffer buf = ByteBuffer.allocate(48);
			while (fileChannel.read(buf) != -1) {
				buf.flip();// 寫模式切換成讀模式
				while (buf.hasRemaining()) {
					System.out.println(buf.get());
				}
				buf.clear();
			}
			aFile.close();//從寫切換到讀
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

    c、buffer的分類:

二、零複製

        buffer默認是在JVM開闢空間、而NIO比BIO在數據處理方面有一個有點:不會將數據從邏輯主存複製到JVM主存

    1. 直接從內核態的數據區讀取數據,不用在copy到jvm堆內存。

    2. 多個數據Buffer不用組合成一個,直接就程序處理了。

    3. 發送的時候,直接發送。

    bytebuffer開闢空間的兩個方法。第二個方法直接在主存開闢空間、不須要在JVM中操做

//直接在JVM開闢空間
public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
}   
//直接在內存開闢空間
public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
}


三、Slector

        普通的I/O調用都會阻塞等待,直到文件數據準備就行才能使用。而NIO則是經過一個單獨的線程不對的去詢問系統I/O數據是否準備好了。準備好後,就能夠經過存放在Selector線程中的key(處理線程的引用)來處理。經過這種主動啓動線程的方式,避免了掉多線程同時啓動,經過CPU切換切換詢問狀態的方式,節約了CPU的開銷。

        a、開啓Selector

Selector selector = Selector.open();

        b、註冊channel到selector

channel.configureBlocking(false);//設置非阻塞。也就是說不能和FileChannel一塊兒使用了
SelectionKey key=channel.register(selector,SelecotionKey.OP_READ);

                SelectionKey:是channel在selector上的註冊標籤。當I/O事件準備好的時候,就會返回須要事件類型:

事件類型 註冊類型 類型判斷
監聽:accept(服務器) SelectionKey.OP_ACCEPT SelctionKey.isAcceptable()
鏈接:connect(客服端、服務器) SelectionKey.OP_CONNECT SelctionKey.isConnectable()
讀:read(客服端、服務器) SelectionKey.OP_READ SelctionKey.isReadable()
寫:write(客服端、服務器) SelectionKey.OP_WRITE SelctionKey.isWritable()

                selectionKey能夠獲取channel、selector,以及添加和獲取附加對象

//這個就是獲取channel、處理數據的方式。
Channel channel = selectionKey.channel();
//這個就是獲取selector,用來處理完事件後從新註冊
Selector selector = selectionKey.selector();
//添加、獲取附加對象。
selectionKey.attch(theObject);
Object attachObj = selectionKey.attachment();

     (slectionKey能夠看做是一個存放channel、附加對象的容,和咱們每次註冊到selector中須要處理的事件方式。造成了一個映射關係。只要事件達成咱們就能夠繼續處理)     

          c、從selector中獲取事件

while(true){
    //第一步:獲取事件,只有當有事件處理的時候,selecotr會返回一個大於0的值
    int readyEvents = selector.select();
    if(readyEvents==0) continue;
    //第二步:獲取事件標籤
    Set<SelectionKey> keys = slector.slectionKeys();
    //第三步:處理事件
    Iterator keyIteraotrs = keys.interator();
    while(keyIterators.hasNext()){
        SelectionKey selectionKey = keyIterators.next();
        //當獲取事件的時候,須要從selector刪掉。
        keys.remove(selectionKey);
        if(selectionKey.isAcceptable()){
            //do something 。。。
            //註冊
        }else if(selectionKey.isConnectable()){
            //do something 。。。
            //註冊        
        }else if(selectionKey.isReadable()){
            //do something 。。。
            //註冊    
        }else if(selectionKey.isWritable()){
            //do something 。。。
            //註冊         
        }
    }
}

四、使用       

        a、文件

try {
			RandomAccessFile fromFile = new RandomAccessFile("D:/project/test/nio/1.txt", "rw");
			FileChannel fromFileChannel = fromFile.getChannel();
			RandomAccessFile toFile = new RandomAccessFile("D:/project/test/nio/2.txt", "rw");
			FileChannel toFileChannel = toFile.getChannel();
			//不一樣channel的數據傳送
			toFileChannel.transferFrom(fromFileChannel, 0, fromFileChannel.size());
			// buffer
//			ByteBuffer buf = ByteBuffer.allocate(48);
//			while (fromFileChannel.read(buf) != -1) {
//				// buf.flip();// 寫模式切換成讀模式
//				while (buf.hasRemaining()) {
//					System.out.println(buf.getChar());
//				}
//				// buf.clear();// 從寫切換到讀
//			}
			fromFile.close();
			toFile.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

        b、serverSocket

// 1.開啓Selector
		Selector selector = Selector.open();
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 二、設置channel的模式(阻塞-false、非阻塞-true)
		serverSocketChannel.socket().bind(new InetSocketAddress(80));
		serverSocketChannel.configureBlocking(false);
		// 二、註冊channel到Selector
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		while (true) {
			int readyChannel = selector.select();
			if (readyChannel == 0)
				continue;
			Set<SelectionKey> keys = selector.selectedKeys();
			Iterator<SelectionKey> keyIterators = keys.iterator();
			while (keyIterators.hasNext()) {
				SelectionKey selectionKey = keyIterators.next();
				keys.remove(selectionKey);
				SocketChannel socketChannel = null;
				if (selectionKey.isAcceptable()) {
					// 訪問事件(這裏須要獲取的serverSocketChannel)
					serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
					socketChannel = serverSocketChannel.accept();
					socketChannel.configureBlocking(false);
					socketChannel.register(selector,
							SelectionKey.OP_WRITE | SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
				} else if (selectionKey.isConnectable()) {
					socketChannel = (SocketChannel) selectionKey.channel();
					socketChannel.configureBlocking(false);
					socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
				} else if (selectionKey.isReadable()) {
					socketChannel = (SocketChannel) selectionKey.channel();
					socketChannel.configureBlocking(false);
					ByteBuffer buff = ByteBuffer.allocateDirect(1024);
					while (socketChannel.read(buff) != -1) {
						buff.flip();
						while (buff.hasRemaining()) {
							System.out.println(buff.getChar());
						}
						buff.clear();
					}
					buff=null;
					socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_WRITE);
				} else if (selectionKey.isWritable()) {
					socketChannel = (SocketChannel) selectionKey.channel();
					socketChannel.configureBlocking(false);
					ByteBuffer buff = ByteBuffer.allocateDirect(1024);
					buff.put(new String("hello , i am Nio !").getBytes());
					buff.flip();
					socketChannel.write(buff);
					buff=null;
					socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ);
				}
			}
		}

       c、socket

// 1.開啓Selector
		Selector selector = Selector.open();
		SocketChannel socketChannel = SocketChannel.open();
		// 二、設置channel的模式(阻塞-false、非阻塞-true)
		socketChannel.configureBlocking(false);
		socketChannel.connect(new InetSocketAddress("http://localhost/", 80));
		// 二、註冊channel到Selector
		socketChannel.register(selector, SelectionKey.OP_CONNECT);
		while (true) {

			// 返回事件
			int readyChannels = selector.select();
			if (readyChannels == 0)
				continue;
			// 有事件發生

			// 返回的key集合(返回的是一個channel集合)
			Set<SelectionKey> keys = selector.selectedKeys();

			// 對每個channel進行處理
			Iterator<SelectionKey> keyIterators = keys.iterator();
			while (keyIterators.hasNext()) {
				SelectionKey selectionKey = keyIterators.next();
				keys.remove(selectionKey);
				// 鏈接發生了
				if (selectionKey.isConnectable()) {
					// 須要將key(channel)移除、由於selector是條件觸發,若是不刪除。下次事件來了會發生問題
					// 從key中獲取channel
					socketChannel = (SocketChannel) selectionKey.channel();
					socketChannel.configureBlocking(false);
					// 從新註冊(前面刪除了註冊)
					socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
				} else if (selectionKey.isReadable()) {
					// 讀取數據
					socketChannel = (SocketChannel) selectionKey.channel();
					socketChannel.configureBlocking(false);
					ByteBuffer buff = ByteBuffer.allocateDirect(1024);
					while (socketChannel.read(buff) != -1) {
						buff.flip();
						while (buff.hasRemaining()) {
							System.out.println(buff.getChar());
						}
						buff.clear();
					}
					buff=null;
					socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_WRITE);
				} else if (selectionKey.isWritable()) {
					// 寫入數據
					socketChannel = (SocketChannel) selectionKey.channel();
					socketChannel.configureBlocking(false);
					ByteBuffer buff = ByteBuffer.allocateDirect(1024);
					buff.put(new String("hello , i am Nio !").getBytes());
					buff.blip();
					socketChannel.write(buff);
					buff=null;
					socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ);
				}
			}
		}

       d、Pipe:兩個線程之間的數據傳送。傳送用sink通道、接受用source通道

public void writeToPipeChannel() throws IOException {
		Pipe pipe = Pipe.open();
		Pipe.SinkChannel sinkChannel = pipe.sink();

		String newData = "New String to write to file ... " + System.currentTimeMillis();
		ByteBuffer buf = ByteBuffer.allocate(48);
		buf.clear();
		buf.put(newData.getBytes());
		buf.flip();
		while (buf.hasRemaining()) {
			sinkChannel.write(buf);
		}
	}

	public void readFromPipeChannel() throws IOException {
		Pipe pipe = Pipe.open();
		Pipe.SourceChannel sourceChannel = pipe.source();

		ByteBuffer buf = ByteBuffer.allocateDirect(48);
		buf.clear();
		while (sourceChannel.read(buf) != -1) {
			buf.flip();
			while (buf.hasRemaining()) {
				System.out.println(buf.getChar());
			}
			buf.clear();
		}
	}

    Pipe原理圖示

相關文章
相關標籤/搜索