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();
}
}
}
}
上一篇: java nio学习之(一)网络编程
下一篇: java nio学习笔记<一>