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();
			}
		}
	}
}

 

 

 

 

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