Java NIO學習總結(一)

Java NIO全稱java non-blocking IO,最早出現於jdk1.4版本中,提供了一種新的IO實現,在一定程度上可以用於替代傳統的IO。
Java NIO主要包含以下三個部分:
    1.Channel
    2.Buffer
    3.Selector

Java NIO的所有功能實現都是基於這三個部分,接下來我分別詳細的介紹一下這幾個接口。


一、Channle

Channle類似於傳統IO中的Stream,數據可以通過Channle進行傳輸,但是也存在一些不同:
    1.Channle中數據的傳輸是雙向的,即可以向Channle中寫數據,也可以從中讀取數據,而IO中的Stream是單向傳輸的。
    2.可以同時向Channle中寫數據和讀取數據。
常用的Channle的實現:
   1.FileChannle:通常使用FileChannle來對文件進行讀操作和寫操作,在FileInputStream和FileOutputStream類中就提供了getChannel()方法來獲取FileChannle對象。
    2.SocketChannel:用於TCP網絡傳輸中的數據讀寫。
    3.ServerSocketChannel:用於TCP網絡傳輸中,服務器端監聽客戶端請求,並獲取到一個SocketChannle對象。
    4.DatagramChannel:用於UDP網絡傳輸中的數據讀寫。
 

二、Buffer

一個buffer就相當於一個內存塊,我們可以向buffer中寫入數據,也可以從buffer中讀取數據。

Buffer是一個抽象類,下面我貼出了對應的源碼:

public abstract class Buffer {


    static final int SPLITERATOR_CHARACTERISTICS =
        Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;

    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;


    long address;


    Buffer(int mark, int pos, int lim, int cap) {
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }

    public final int capacity() {
        return capacity;
    }

    public final int position() {
        return position;
    }

    public final Buffer position(int newPosition) {
        if ((newPosition > limit) || (newPosition < 0))
            throw new IllegalArgumentException();
        position = newPosition;
        if (mark > position) mark = -1;
        return this;
    }

    public final int limit() {
        return limit;
    }

    public final Buffer limit(int newLimit) {
        if ((newLimit > capacity) || (newLimit < 0))
            throw new IllegalArgumentException();
        limit = newLimit;
        if (position > limit) position = limit;
        if (mark > limit) mark = -1;
        return this;
    }

    public final Buffer mark() {
        mark = position;
        return this;
    }

    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

    public final int remaining() {
        return limit - position;
    }

    public final boolean hasRemaining() {
        return position < limit;
    }

    public abstract boolean isReadOnly();

    public abstract boolean hasArray();

    public abstract Object array();

    public abstract int arrayOffset();

    public abstract boolean isDirect();


    final int nextGetIndex() {
        if (position >= limit)
            throw new BufferUnderflowException();
        return position++;
    }

    final int nextGetIndex(int nb) {
        if (limit - position < nb)
            throw new BufferUnderflowException();
        int p = position;
        position += nb;
        return p;
    }

    final int nextPutIndex() {
        if (position >= limit)
            throw new BufferOverflowException();
        return position++;
    }

    final int nextPutIndex(int nb) {
        if (limit - position < nb)
            throw new BufferOverflowException();
        int p = position;
        position += nb;
        return p;
    }

    final int checkIndex(int i) { 
        if ((i < 0) || (i >= limit))
            throw new IndexOutOfBoundsException();
        return i;
    }

    final int checkIndex(int i, int nb) { 
        if ((i < 0) || (nb > limit - i))
            throw new IndexOutOfBoundsException();
        return i;
    }

    final int markValue() { 
        return mark;
    }

    final void truncate() { 
        mark = -1;
        position = 0;
        limit = 0;
        capacity = 0;
    }

    final void discardMark() {
        mark = -1;
    }

    static void checkBounds(int off, int len, int size) {
        if ((off | len | (off + len) | (size - (off + len))) < 0)
            throw new IndexOutOfBoundsException();
    }

}

Buffer中有四個重要的值,分別是mark、limit、position、capacity:

1.capacity:表示一個Buffer所分配的最大容量,一般在初始化Buffer對象是指定,如:

ByteBuffer buffer = ByteBuffer.allocate(1024);

同ByteBuffer的靜態方法爲buffer對象指定的了1024個字節的空間。

2. position:指當前可寫入或讀取的位置,默認值爲0,當向buffer中寫入一定字節的數據時,positon的值會處於之前寫入值得後一個字節且position<=capacity。通常我們在向一個buffer中寫入數據之後,想要再讀取其中的數據,需要調用flip()方法,將position的值置爲0,以保證從第一個字節開始讀取。

3.limit:表示當前可以向buffer中寫入數據或者讀取數據的大小。通常在寫模式下,limit值與capacity相同,在讀模式下,limit值與寫模式下的position值相同(當寫完數據之後調用了flip()方法,position=0,limit的值與調用方法之前的position值相同)。

4.mark:用於標記當前讀取或寫入的位置,即記錄當前position的值,使得在調用reset() 方法時,可以從之前記錄的位置開始操作數據。

Buffer中的幾個重要方法:

1.flip():將limit值置爲position的值,並將position置爲1,通常用於寫操作轉換爲讀操作的過程。

2.clear():清空buffer的數據(實際並沒有清空數據),將mark、limit、position、capacity都還原爲初值。

3.mark()和reset():分別用於標記當前position位置,並與稍後還原。

 

三、Selector

Selector一般稱爲選擇器,可以用於監控控多個 SelectableChannel 的 IO ,即使用單線程來管理多個Channel,是非阻塞IO的核心。在傳統的Socketbian編程中,當客戶端向服務端發起多個請求時,一般情況下,服務端會開啓一個線程來處理對應的請求,但是如果客戶端想要與服務端保持長時間的連接,並且不是連續的向服務端發送請求,這時如果使用多線程會佔用太多無用的內存,因此需要使用Selector。

1.Selector的使用方式

(1)調用Selector的靜態方法open()創建一個Selector對象

Selector selector = Selector.open();

(2)將Channle設置爲非阻塞模式並註冊至監聽器

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

這裏的channel必須是非阻塞的,一般繼承自SelectableChannel。

2.Selector監聽事件類型

channel的register方法的第二個參數表示selector的監聽事件類型,是一個interest集合,表示在selector監聽channel事件時,對什麼事件感興趣,包含以下四種監聽事件類型:

(1)SelectionKey.OP_READ:讀就緒

(2)SelectionKey.OP_WRITE:寫就緒

(3)SelectionKey.OP_CONNECT:連接就緒

(4)SelectionKey.OP_ACCEPT:接收就緒

當監聽器需要一次監聽多個事件,可以使用

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

3.SelectionKey

一個SelectionKey鍵值表示一個特定通道和一個選擇器之間的對應的註冊關係,包含以下幾種主要方法:

(1)key.interestOps():獲取選擇器監聽事件,可以通過以下方式來進行判斷:

int interestSet = key.interestOps(); 
			
System.out.println("isInterestedInAccept = " + ((interestSet & SelectionKey.OP_ACCEPT) 
					== SelectionKey.OP_ACCEPT));
System.out.println("isInterestedInConnect = " + ((interestSet & SelectionKey.OP_CONNECT) 
					== SelectionKey.OP_CONNECT));
System.out.println("isInterestedInRead = " + ((interestSet & SelectionKey.OP_READ) 
					== SelectionKey.OP_READ));
System.out.println("isInterestedInWrite = " + ((interestSet & SelectionKey.OP_WRITE) 
					== SelectionKey.OP_WRITE));

(2)key.selector():返回對應的selector

(3)key.channel():返回對應的channel

(4)key.readyOps():表示通道所處的就緒操作的集合

(5)key.isReadable():是否可讀,boolean類型

(6)key.isWritable():是否可寫,boolean類型

(7)key.isConnectable():是否可以連接,boolean類型

(8)key.isAcceptable():是否可以接收,boolean類型

 

四、一個簡單的客戶端與服務端交互的實例

1.服務端


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.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class NIOServer {

	private static Map<SelectionKey, StringBuilder> selectionMap = new HashMap<SelectionKey, StringBuilder>();

	public static void main(String[] args) {

		ServerSocketChannel serverSocketChannel = null;
		Selector selector = null;
		try {
			serverSocketChannel = ServerSocketChannel.open();
			serverSocketChannel.bind(new InetSocketAddress(9999));
			// 將channel設置爲非阻塞
			serverSocketChannel.configureBlocking(false);

			selector = Selector.open();
			// 將channel註冊時選擇器,並指定“感興趣”的事件爲accept
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

			while (true) {

				int select = selector.select();
				System.out.println("select = " + select);

				Set<SelectionKey> keySet = selector.selectedKeys();
				Iterator<SelectionKey> iterator = keySet.iterator();

				while (iterator.hasNext()) {

					SelectionKey selectionKey = iterator.next();

					if (selectionKey.isAcceptable()) {
						ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();
						SocketChannel socketChannel = serverChannel.accept();
						if (null == socketChannel) {
							continue;
						}
						// 將獲取的連接註冊至選擇器,並監聽讀事件
						socketChannel.configureBlocking(false);
						socketChannel.register(selector, SelectionKey.OP_READ);

					} else if (selectionKey.isReadable()) {

						SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

						StringBuilder sb = selectionMap.get(selectionKey);
						if (null == sb) {
							sb = new StringBuilder();
							selectionMap.put(selectionKey, sb);
						}
						ByteBuffer buffer = ByteBuffer.allocate(10);
						int n = -1;
						while ((n = socketChannel.read(buffer)) > 0) {
							System.out.println(n);
							buffer.flip();
							sb.append(new String(buffer.array(), 0, n));
							buffer.clear();
						}
						System.out.println(n);
						if (n == -1) {
							selectionMap.remove(selectionKey);
							System.out.println("receive message = " + sb.toString());
							// 關閉輸入
							socketChannel.shutdownInput();
							// 將感興趣事件改爲寫
							selectionKey.interestOps(SelectionKey.OP_WRITE);
						}
					} else if (selectionKey.isWritable()) {

						SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
						ByteBuffer buffer = ByteBuffer.allocate(1024);
						buffer.put("Hello Client".getBytes());
						buffer.flip();
						socketChannel.write(buffer);
						// 關閉輸出
						socketChannel.shutdownOutput();
						socketChannel.close();
						selectionKey.channel();
					}
					iterator.remove();
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (null != serverSocketChannel) {
					serverSocketChannel.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

2.客戶端


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

public class NIOClient {

	public static void main(String[] args) {

		SocketChannel socketChannel = null;

		try {
			socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
			socketChannel.configureBlocking(false);

			ByteBuffer buffer = ByteBuffer.allocate(10240);
			buffer.put("Hello Server ".getBytes());
			buffer.flip();

			socketChannel.write(buffer);

			// 關閉輸出
			socketChannel.shutdownOutput();

			buffer.clear();
			StringBuilder sb = new StringBuilder();
			int n = -1;
			while ((n = socketChannel.read(buffer)) != -1) {
				sb.append(new String(buffer.array(), 0, n));
				buffer.clear();
			}
			// 關閉輸入
			socketChannel.shutdownInput();

			System.out.println("receive message = " + sb.toString());
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (null != socketChannel) {
					socketChannel.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章