开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
基本介绍
NIO 是从java1.4版本开始引入的一个全新的IO API,NIO是面向缓冲区、基于通道的IO操作。NIO有三大核心部分:Channel、Buffer、Selector。
NIO与BIO的比较
- BIO以流的方式处理数据,NIO以块的方式处理数据
- BIO是阻塞的,NIO是非阻塞的
- BlO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道
通道、缓冲区、选择器
通道
通道Channel是对原I/O包中的流的模拟,可以通过它来读取或写入数据。
通道和流不同的是,流是单向的,而通道是双向的,既可以读也可以写
通道的类型:
- FileChannel:和文件相关读写
- DatagramChannel:通过UDP读写
- SocketChannel:通过TCP读写
- ServerSocketChannel:可以监听新进来的TCP链接,对每个新链接创建一个SocketChannel
缓冲区
缓冲区的作用是,发送给通道的数据都必须先放入到缓冲区中,同样的,读取数据也一样,的先把通道中的数据放到缓冲区中。
缓冲区类型
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
缓冲区重要的变量
- capacity:最大容量
- position:当前已经读写的字节数
- limit:剩余可读写字节数
以上三个属性值之间有一些相对大小的关系:0 <= position <= limit <= capacity
举个例子:
初始化一个容量大小为10的XXBuffer对象,初始化的时候position为0,limit和capacity为10,在后续使用时capacity值固定不变,而另外两个值是会发生变化。
假设现在从channel中读取5个数据,此时position为4,而limit仍然是10
下面将读取的数据写入到缓冲区,首先需要调用flip()方法,这个方法会做两件事
1、将limit值设置为position
2、将position值设置为0
在将缓冲区的数据写入到通道中,此时position的值增加,limit保持不变,但是position不会超过limit
从缓冲区读取数据完毕后,调用clear()方法能够把状态值设为初始化值
代码演示\
package com.example.io.nio;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class demo {
public static void main(String[] args) throws IOException {
FileInputStream fin = new FileInputStream("/Users/victor/workspace/demo/src/main/java/com/example/io/nio/test.txt");
FileChannel fc = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10);
output("初始化", buffer);
fc.read(buffer);
output("调用read()", buffer);
buffer.flip();
output("调用flip()", buffer);
while (buffer.remaining() > 0) {
byte b = buffer.get();
}
output("调用get()", buffer);
buffer.clear();
output("调用clear()", buffer);
fin.close();
}
public static void output(String step, Buffer buffer) {
System.out.println(step + " : ");
System.out.print("capacity: " + buffer.capacity() + ", ");
System.out.print("position: " + buffer.position() + ", ");
System.out.println("limit: " + buffer.limit());
System.out.println();
}
}
文件内容:
结果 :
选择器
NIO实现了IO的多路复用,一个线程Thread使用一个Selector轮询监听多个channel上的事件,从而让一个线程可以处理多个事件。
创建选择器
Selector selector = Selector.open();
将通道注册到选择器
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
选择器的具体事件
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
NIO实例
package com.example.io.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
ServerSocket serverSocket = ssChannel.socket();
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
serverSocket.bind(address);
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();
// 服务器会为每个新连接创建一个 SocketChannel
SocketChannel sChannel = ssChannel1.accept();
sChannel.configureBlocking(false);
// 这个新连接主要用于从客户端读取数据
sChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sChannel = (SocketChannel) key.channel();
System.out.println(readDataFromSocketChannel(sChannel));
sChannel.close();
}
keyIterator.remove();
}
}
}
private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuilder data = new StringBuilder();
while (true) {
buffer.clear();
int n = sChannel.read(buffer);
if (n == -1) {
break;
}
buffer.flip();
int limit = buffer.limit();
char[] dst = new char[limit];
for (int i = 0; i < limit; i++) {
dst[i] = (char) buffer.get(i);
}
data.append(dst);
buffer.clear();
}
return data.toString();
}
}
package com.example.io.nio;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class NIOClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream out = socket.getOutputStream();
String s = "hello world";
out.write(s.getBytes());
out.close();
}
}
NIO的优点
- NIO是非阻塞的
- NIO是块处理,相较于I/O更具优势