一、概述
Java NIO(New IO)是一个可替代Java IO API(从Java1.4开始),JAVA NIO提供了与标准IO不同的工作方式。
Java NIO:Channels and Buffers(通道和缓冲区)
标准的IO基于字节流或者字符流进行操作,而NIO基于通道和缓冲区进行操作,数据是总是从通道读取的缓冲区中,或者从缓冲区写入到通道中。
Java NIO:Non-blocking IO(非阻塞IO):Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情,当数据被写入缓冲区时,线程可以继续处理它,从缓冲区写入通道也类似。
Java NIO:Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
1.1、NIO SelectionKey中定义的4种事件
- SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
- SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功
- SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了)
- SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作)
这里 注意,下面两种,SelectionKey.OP_READ ,SelectionKey.OP_WRITE ,
1.当向通道中注册SelectionKey.OP_READ事件后,如果客户端有向缓存中write数据,下次轮询时,则会 isReadable()=true;
2.当向通道中注册SelectionKey.OP_WRITE事件后,这时你会发现当前轮询线程中isWritable()一直为ture,如果不设置为其他事件
1.2、与传统IO区别
IO | NIO |
面向流 | 面向缓冲区 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
1.3、ByteBuffer
SocketChannel.read(ByteBuffer dst)
和SocketChannel.write(ByteBuffer src)
的方法中的参数则都变为了java.nio.ByteBuffer
,该类型就是JavaNIO对byte数组的一种封装
ByteBuffer包含几个基本的属性:
- position:当前的下标位置,表示进行下一个读写操作时的起始位置;
- limit:结束标记下标,表示进行下一个读写操作时的(最大)结束位置;
- capacity:该ByteBuffer容量;
- mark: 自定义的标记位置;
ByteBuffer的基本用法就是:
初始化(allocate
)–> 写入数据(read / put
)–> 转换为写出模式(flip
)–> 写出数据(get
)–> 转换为写入模式(compact
)–> 写入数据(read / put
)…
public static void main(String args[]) throws FileNotFoundException {
System.out.println("----------Test allocate--------");
System.out.println("before alocate:"
+ Runtime.getRuntime().freeMemory());
// 如果分配的内存过小,调用Runtime.getRuntime().freeMemory()大小不会变化?
// 要超过多少内存大小JVM才能感觉到?
ByteBuffer buffer = ByteBuffer.allocate(102400);
System.out.println("buffer = " + buffer);
System.out.println("after alocate:"
+ Runtime.getRuntime().freeMemory());
// 这部分直接用的系统内存,所以对JVM的内存没有影响
ByteBuffer directBuffer = ByteBuffer.allocateDirect(102400);
System.out.println("directBuffer = " + directBuffer);
System.out.println("after direct alocate:"
+ Runtime.getRuntime().freeMemory());
System.out.println("----------Test wrap--------");
byte[] bytes = new byte[32];
buffer = ByteBuffer.wrap(bytes);
System.out.println(buffer);
buffer = ByteBuffer.wrap(bytes, 10, 10);
System.out.println(buffer);
}
public static void main(String args[]){
System.out.println("--------Test reset----------");
buffer.clear();
buffer.position(5);
buffer.mark();
buffer.position(10);
System.out.println("before reset:" + buffer);
buffer.reset();
System.out.println("after reset:" + buffer);
System.out.println("--------Test rewind--------");
buffer.clear();
buffer.position(10);
buffer.limit(15);
System.out.println("before rewind:" + buffer);
buffer.rewind();
System.out.println("before rewind:" + buffer);
System.out.println("--------Test compact--------");
buffer.clear();
buffer.put("abcd".getBytes());
System.out.println("before compact:" + buffer);
System.out.println(new String(buffer.array()));
buffer.flip();
System.out.println("after flip:" + buffer);
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
System.out.println("after three gets:" + buffer);
System.out.println("\t" + new String(buffer.array()));
buffer.compact();
System.out.println("after compact:" + buffer);
System.out.println("\t" + new String(buffer.array()));
System.out.println("------Test get-------------");
buffer = ByteBuffer.allocate(32);
buffer.put((byte) 'a').put((byte) 'b').put((byte) 'c').put((byte) 'd')
.put((byte) 'e').put((byte) 'f');
System.out.println("before flip()" + buffer);
// 转换为读取模式
buffer.flip();
System.out.println("before get():" + buffer);
System.out.println((char) buffer.get());
System.out.println("after get():" + buffer);
// get(index)不影响position的值
System.out.println((char) buffer.get(2));
System.out.println("after get(index):" + buffer);
byte[] dst = new byte[10];
buffer.get(dst, 0, 2);
System.out.println("after get(dst, 0, 2):" + buffer);
System.out.println("\t dst:" + new String(dst));
System.out.println("buffer now is:" + buffer);
System.out.println("\t" + new String(buffer.array()));
System.out.println("--------Test put-------");
ByteBuffer bb = ByteBuffer.allocate(32);
System.out.println("before put(byte):" + bb);
System.out.println("after put(byte):" + bb.put((byte) 'z'));
System.out.println("\t" + bb.put(2, (byte) 'c'));
// put(2,(byte) 'c')不改变position的位置
System.out.println("after put(2,(byte) 'c'):" + bb);
System.out.println("\t" + new String(bb.array()));
// 这里的buffer是 abcdef[pos=3 lim=6 cap=32]
bb.put(buffer);
System.out.println("after put(buffer):" + bb);
System.out.println("\t" + new String(bb.array()));
}
1.4直接缓冲区和间接缓冲区
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中,当从物理磁盘读取数据的时候,先读取到物理空间(其实还是在设备上的),再copy到jvm空间,然后才从jvm空间里进行读取,由于这里涉及到一个copy的过程,所以效率相比直接缓冲区较低。IO缓冲区属于非直接,大多数缓冲区都是非直接缓冲区
直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率。非常占内存,安全性相比非直接缓冲区较低
二、样例
public static void main(String[] args) throws IOException {
System.out.println("Listening for connection on port " + DEFAULT_PORT);
Selector selector = Selector.open();
initServer(selector);
//开始监听
while (true) {
selector.select(); //监控所有注册的 channel,当没有可用的IO 操作时会阻塞,有可用的IO 操作时往下执行
for (Iterator itor = selector.selectedKeys().iterator(); itor.hasNext();) { //可进行 IO 操作的 channel的集合:selectedKeys
SelectionKey key = (SelectionKey) itor.next();
itor.remove();
try {
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel(); //获取Accept操作的Channel
SocketChannel client = server.accept(); //客户端的SocketChannel
System.out.println("Accepted connection from " + client);
client.configureBlocking(false); //客户端配置为非阻塞
SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ); //把客户端套接字通道 注册到selector,并注明为OP_READ操作
ByteBuffer buffer = ByteBuffer.allocate(100);
clientKey.attach(buffer); 客户端SelectionKey附上一个ByteBuffer
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel(); //获取客户端的SocketChannel
ByteBuffer buffer = (ByteBuffer) key.attachment(); buffer .clear();
int n = client.read(buffer); //将客户端套接字通道的数据 读取到缓冲区
if (n > 0) {
receiveText = new String(buffer.array(),0,n);//接受到的数据
client.register(selector, SelectionKey.OP_WRITE); // switch to OP_WRITE
}
} else if (key.isWritable()) {
System.out.println("is writable...");
SocketChannel client = (SocketChannel) key.channel(); //获取客户端的SocketChannel
ByteBuffer buffer = (ByteBuffer) key.attachment(); buffer.clear();buffer.put(sendText.getBytes()); //sendText :"message from server"
buffer.flip(); client.write(buffer); //输出到通道
if (buffer.remaining() == 0) { // write finished, switch to OP_READ
client.register(selector, SelectionKey.OP_READ);
}
}
} catch (IOException e) {
key.cancel();
try { key.channel().close(); } catch (IOException ioe) { }
}
}
}
}