三大组件之buffer和selector
2. buffer缓存区
NIO核心在于通道channel和缓存区buffer。就是channel是用于连接IO设备的,而buffer则是存储数据,缺一不可。channel用来传输,buffer则是存储。
buffer是由java.nio定义好的,有不同的实现类。常用的子类都是基本类型出去Boolean类型之外的。xxxBuffer。获取方式都是
ByteBuffer bytebuffer = ByteBuffer.allocate(1024);
ByteBuffer bytebuffer = ByteBuffer.wrap("asd".getBytes(StandardCharsets.UTF_8));
而且缓冲区提供了两个核心方法:get()和put(),put方法是将数据存入到缓冲区,而get是获取缓冲区的数据。
Buffer基本属性
private int mark = -1;//设置标记,标记是一个索引,通过Buffer中的mark()方法指定Buffer中的一个特定的position,之后可以通过调用reset()方法恢复到这个position。
private int position = 0; //从position开始读写
private int limit; //限制,读写模式转化依赖于此,limit位置之后不可读写
private int capacity;// 容量就是一旦申请成功不可更改。
mark <= position <= limit <= capacity
代码示例:
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(buffer.capacity());//10
System.out.println(buffer.position());//0
buffer.put("abc".getBytes(StandardCharsets.UTF_8));
System.out.println(buffer.position());//3
buffer.flip();//切换读写模式,position从0开始,limit为当前读写位置
System.out.println(buffer.position());//0
System.out.println(buffer.limit());//3
其余方法:
rewind()方法,可重复读,clear()清空缓冲区(数据还在)。hasRemaining()方法就是还有没有数据
直接缓冲区和非直接缓冲区:
非直接缓冲区:通过allocate()方法来分配缓冲区。将缓冲区建立在JVM的内存中。
直接缓冲区:通过allocateDirect()方法分配缓冲区,将缓冲区建立在物理内存中。效率更高。
非直接缓存区,传输数据流程:程序读取数据,发起请求,先到磁盘将数据读到内核中,然后内核拷贝数据到用户态,然后再是通过read返回数据。此时发现有一个拷贝操作多余
直接缓存区,就是没了这一次copy操作,磁盘数据到物理内存映射文件–>程序。
3.Selector
选择器是什么:就是在客户端到服务器直接使用通道的注册器。我的理解是:客户端要发送数据给服务端首先在Selector注册一个通道。服务端的选择器就会去监听这个channel的io状态(读写,连接),有数据到了才会调用服务器的一个线程去处理。
做什么用的:用于检查一个或多个 Channel(通道)的状态是否处于连接就绪、接受就绪、可读就绪、可写就绪。
使用流程:
- 创建一个选择器一般是通过 Selector 的工厂方法,Selector.open
Selector selector = Selector.open();
- 将一个通道注册进入选择器
//创建一个 TCP 套接字通道
SocketChannel channel = SocketChannel.open();
//调整通道为非阻塞模式
channel.configureBlocking(false);
//向选择器注册一个通道
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
第二个参数表示的是当前选择器感兴趣通道的哪些事件,
int OP_READ = 1 << 0;
int OP_WRITE = 1 << 2;
int OP_CONNECT = 1 << 3;
int OP_ACCEPT = 1 << 4;
- register()返回一个SelectionKey实例。可以通过它返回当前相关联的选择器实例,也可以调用它的 channel 方法返回当前关联关系中的通道实例。
- Set keys = selector.selectedKeys();返回注册在选择器中的多个通道的状态。
- 成功连接到另一台服务器的通道是“连接Connect就绪”。 接受传入连接的服务器套接字通道是“接受Accept”就绪。 准备好读取数据Read的通道已经准备就绪。 一个准备好写入数据的通道Write已经准备好了
代码示例:客户端和服务端的非阻塞代码:
//客户端
package nio;
import com.sun.org.apache.bcel.internal.classfile.ConstantUtf8;
import sun.awt.CharsetString;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Scanner;
public class nio1 {
public static void main(String[] args) throws Exception {
//1.创建一个TCP客户端
SocketChannel sc = SocketChannel.open(new InetSocketAddress("192.168.0.7",8888));
//2.设置为非阻塞模式
sc.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String str = scanner.next();
// 3.发送数据给服务端,直接将数据存储到缓冲区
byteBuffer.put((new Date().toString()+str).getBytes());
// 4.将缓冲区的数据写入到sChannel
byteBuffer.flip();
sc.write(byteBuffer);
byteBuffer.clear();
}
//5.关闭
sc.close();
}
}
//服务端
package nio;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
public class nioServer {
public static void main(String[] args) throws Exception{
//1.创建一个选择器
Selector sel = Selector.open();
//2.创建一个TCP服务端
ServerSocketChannel ssc = ServerSocketChannel.open();
//3.设置非阻塞+绑定端口
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(8888));
//4.注册通道到选择器,并且监听通道的连接事件
ssc.register(sel, SelectionKey.OP_ACCEPT);
//5.通过轮询判断连接是否就绪,如果大于0,至少有一个SelectionKey准备就绪
//此时是客户端连接到了服务端
System.out.println("服务端启动:"+ssc.getLocalAddress());
while(sel.select() > 0) {
System.out.println("有连接");
//6.获取已经连接就绪的所有通道的SelectedKey
Iterator<SelectionKey> iterator = sel.selectedKeys().iterator();
//7.遍历,然后根据不同的事件做不同的处理
while(iterator.hasNext()) {
System.out.println("in");
SelectionKey selectionKey = iterator.next();
//接收就绪,此时是接受传入连接的服务器套接字连接通道就绪
//就是serverSocket可以获取了,并且注册到选择器中
if (selectionKey.isAcceptable()) {
System.out.println("in in");
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//注册不仅一个事件,SelectionKey.OP_READ|SelectionKey.OP_WRITE
sc.register(sel,SelectionKey.OP_READ);
}else if (selectionKey.isReadable()) {
System.out.println("else in");
//读事件就绪,通过selectionKey获取通道
SocketChannel channel = (SocketChannel)selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(channel.read(buffer) > 0) {
buffer.flip();
System.out.println(new String(buffer.array(),StandardCharsets.UTF_8));
buffer.clear();
}
}
// 当selectionKey使用完毕需要移除,否则会一直优先
iterator.remove();
}
}
}
}