Java之NIO基础介绍(二)

三大组件之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(通道)的状态是否处于连接就绪、接受就绪、可读就绪、可写就绪。
使用流程:

  1. 创建一个选择器一般是通过 Selector 的工厂方法,Selector.open

    Selector selector = Selector.open();
    
  2. 将一个通道注册进入选择器

    //创建一个 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;

  3. register()返回一个SelectionKey实例。可以通过它返回当前相关联的选择器实例,也可以调用它的 channel 方法返回当前关联关系中的通道实例。

  4. Set keys = selector.selectedKeys();返回注册在选择器中的多个通道的状态。

  5. 成功连接到另一台服务器的通道是“连接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();
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值