一篇通俗易懂的文章初探NIO

Java NIO

简介

什么是Java NIO

NIO :non-blocking IO (非阻塞IO)

Java NIO 是 Java1.4 引入的一个新的IO API

NIO与IO有同样的作用和目的,都是为了读写文件

NIO与IO的区别
NIOIO
非阻塞IO阻塞IO
面向缓冲区(双向)面向流(单向)
选择器

总结:NIO支持面向缓冲区(Buffer),通道(channel)的IO操作,使用选择器来实现非阻塞IO,以更高效的方式进行文件的读写

下文将对NIO的三个重要的新名词:缓冲区(Buffer),通道(Channel),选择器(selector) 进行描述

通道与缓冲区

通道是打开IO设备的连接,缓冲区是用于容纳数据的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述

通道用于传输,缓冲区用于存取

每个通道可以连接一个IO设备,所以读写文件一般使用两个通道

可以想象缓冲区运行在通道上,所以一般本地读写文件使用一个缓冲区即可

缓冲区(Buffer)
缓冲区的结构

java.nio包中有Buffer抽象类,它有着除boolean类型外的代表七个基本类型的子类

在这里插入图片描述

它们基本类似,只是各自管理的数据类型不同(后面使用ByteBuffer来举例)

实际上这些缓冲区使用数组来实现的

比如:ByteBuffer就是用byte[]来实现

缓冲区又分为直接缓冲区和非直接缓冲区

  • 非直接缓冲区

在这里插入图片描述

  • 直接缓冲区

    在这里插入图片描述

非直接缓冲区:读取物理磁盘中的某个文件时,会把读到的数据放在内核地址中,然后经过copy到JVM的用户地址空间(写的过程类似)都需要一个copy的步骤

直接缓冲区:将缓冲区建立在物理内存中,通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,避免Java堆和物理内存来回复制数据

缓冲区的字段(属性)

Buffer中有四个重要的属性

// Invariants: mark <= position <= limit <= capacity
//标记索引,通过reset()回到此位置
private int mark = -1;
//操作下一个数据的索引
private int position = 0;
//分界线索引
private int limit;
//缓冲区总容量,因为底层是数组,所以不可变
private int capacity;

缓冲区有读,写两种模式,默认写,使用flip()方法切换成读模式

切换成读模式的时候,之前写到的最后位置就是limit分界线的位置

属性之间需要满足的关系:mark <= position <= limit <= capacity

缓冲区的方法

Buffer的子类提供了操作数据的两个重要方法:

  1. put():在缓冲区上写数据
  2. get():在缓冲区上读数据

其中有很多重载方法,方便多种实现

可以通过静态方法allocate()来创建非直接缓冲区

public static ByteBuffer allocate(int capacity) 

通过静态方法allocateDirect()来创建直接缓冲区只有ByteBuffer有直接缓冲区(只有ByteBuffer有这个方法)

这个DirectByteBuffer对象就是在堆中操作那块物理内存的引用

public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
}

还可以通过FileChannel.map()通道方法(后文会介绍)来创建DirectByteBuffer的父类一个MappingByteBuffer映射字节缓冲区(也是直接缓冲区)

public abstract MappedByteBuffer map(MapMode mode,
                                         long position, long size)
        throws IOException;
//模式,当前位置,总量
//模式:

		/**只读
         * Mode for a read-only mapping.
         */
        public static final MapMode READ_ONLY
            = new MapMode("READ_ONLY");

        /**读写
         * Mode for a read/write mapping.
         */
        public static final MapMode READ_WRITE
            = new MapMode("READ_WRITE");

        /**写时复制
         * Mode for a private (copy-on-write) mapping.
         */
        public static final MapMode PRIVATE
            = new MapMode("PRIVATE");

使用方法isDirect()来判断此缓冲区是否是一个直接缓冲区

//创建非直接缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//创建直接缓冲区 直接缓冲区只能是Byte
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

System.out.println("buffer.isDirect():buffer是直接缓冲区吗? "+buffer.isDirect());//false
System.out.println("directBuffer.isDirect():buffer是直接缓冲区吗? "+directBuffer.isDirect());//true
深入了解缓冲区的属性与方法

接下来看一段代码,详细的了解缓冲区的属性与方法

import org.junit.Test;

import java.nio.Buffer;
import java.nio.ByteBuffer;

/**
 * @author Tc.l
 * @Date 2020/10/24
 * @Description: 缓冲区负责数据的存取(boolean类型无, 其他七大数据类型都有)
 */
public class _1缓冲区的数据读取 {
    @Test
    public void test() {
        //allocate(): 新建一个容量为1024的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        lookFiled(buffer,"allocate(): 新建一个容量为1024的缓冲区");

        //put(): 将数据存入缓冲区
        String s = "abcdef";
        buffer.put(s.getBytes());
        lookFiled(buffer,"put(): 将数据abcdef存入缓冲区");


        //flip() 切换读模式
        buffer.flip();
        System.out.println("切换为读模式");

        //get(): 读缓冲区中的数据
        byte[] bytes = new byte[buffer.limit()];
        buffer.get(bytes,0,2);
        lookFiled(buffer,"get(): 读2个缓冲区中的数据");

        //读到的数据
        System.out.print("读到的数据:");
        for (byte b : bytes) {
            if (b==0){
                break;
            }
            System.out.print((char)b);
        }
        System.out.println();

        //mark(): 标记位置
        buffer.mark();
        System.out.println("mark标记当前位置");

        //get(): 读缓冲区中的数据
        buffer.get(bytes,2,2);
        lookFiled(buffer,"get(): 再读2个缓冲区中的数据");

        //读到的数据
        System.out.print("读到的数据:");
        for (byte b : bytes) {
            if (b==0){
                break;
            }
            System.out.print((char)b);
        }
        System.out.println();


        //reset(): 恢复到mark标记的位置
        buffer.reset();
        lookFiled(buffer,"reset(): 恢复到mark标记的位置");

        //rewind():回到0位置
        buffer.rewind();
        lookFiled(buffer,"rewind():回到0位置");
    }

    private void lookFiled(Buffer buffer,String s) {
        System.out.println();
        System.out.println("================"+s+"======================");
        System.out.println("position当前位置:" + buffer.position());
        System.out.println("limit分界线位置:" + buffer.limit());
        System.out.println("capacity总容量位置:" + buffer.capacity());
        if (buffer.hasRemaining()){
            System.out.println("remaining缓冲区中还可以操作的数量:"+buffer.remaining());
        }
        System.out.println();
    }

}

输出内容:

/*
================allocate(): 新建一个容量为1024的缓冲区======================
position当前位置:0
limit分界线位置:1024
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:1024


================put(): 将数据abcdef存入缓冲区======================
position当前位置:6
limit分界线位置:1024
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:1018

切换为读模式

================get(): 读2个缓冲区中的数据======================
position当前位置:2
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:4

读到的数据:ab
mark标记当前位置

================get(): 再读2个缓冲区中的数据======================
position当前位置:4
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:2

读到的数据:abcd

================reset(): 恢复到mark标记的位置======================
position当前位置:2
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:4


================rewind():回到0位置======================
position当前位置:0
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:6
*/
通道(Channel)
通道的结构

通道用于连接IO设备,本身不能直接访问数据,只能与Buffer交互

通道基本都在java.nio.channels包下

  • Channel接口四个重要的实现类
    1. FileChannel 读写操作文件的通道
    2. DatagramChannel 通过UDP读写网络中的数据通道
    3. SocketChannel 通过TCP读写网络中的数据
    4. ServerSocketChannel 服务端监听新来的TCP连接,为每个新连接创建一个SocketChannel
通道的方法
  • 获得通道的三个方式

    1. 对支持通道的类,提供了getChannel()方法
    2. JDK 7 各个通道提供了open(Path path, OpenOption… options)静态方法
    3. JDK 7 工具类Files.newByteChannel(Path path, OpenOption… options)
  • 通道的读写方法

    • Channel.read(Buffer) : 读通道的数据写到缓冲区中

      • 对于通道来说: 这是在读
      • 对于缓冲区来说: 这是在写
    • Channel.write(Buffer) :读缓冲区的数据写到通道中

      • 对于通道来说: 这是在写
      • 对缓冲区来说: 这是在读

      因为对缓冲区来说在读,所以调用write()方法前,记得使用flip()让缓冲区切换为读模式

    通道的read(),write()方法都是以通道作为本身来设计的

深入了解非直接与直接缓冲区的区别

接下来用一段代码进行本地文件的读写操作,并展示缓冲区,通道的使用

非直接与直接缓冲区+通道运输
	/**
     * 通道传输 + 非直接缓冲区
     * 44秒
     */
    @Test
    public  void test1() {
        LocalDateTime start = LocalDateTime.now();
        try (
                //原文件
                FileInputStream fis = new FileInputStream("D:\\QLDownload\\熊出没·奇幻空间\\熊出没·奇幻空间 熊出没奇幻空间 蓝光(1080P).qlv");
                //目标文件
                FileOutputStream fos = new FileOutputStream("D:\\QLDownload\\熊出没·奇幻空间\\2.qlv");

                //获得连接目标文件的通道
                FileChannel outChannel = fos.getChannel();
                //获得连接源文件的通道
                FileChannel inChannel = fis.getChannel();
        ) {
            //创建非直接缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
            //管道.read(缓冲区): 读管道中的数据写到缓冲区中
            while ((inChannel.read(buffer) != -1)) {
                //缓冲区切换为读模式
                buffer.flip();
                //管道.write(缓冲区): 读缓冲区的数据写到管道中
                outChannel.write(buffer);
                //清空缓冲区
                buffer.clear();
            }


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        LocalDateTime end = LocalDateTime.now();
        Duration between = Duration.between(start, end);
        System.out.println(between.getSeconds() + "秒");
    }


    /**
     * 通道传输 + 直接缓冲区
     * 我的情况:
     * 一运行程序,文件夹中的视频就已经复制好了
     * 但是程序需要JVM去垃圾收集掉直接缓冲区,程序才会结束 所以需要很久
     *
     * 107秒 无System.gc();
     * 9秒   有System.gc();
     * System.gc() 不一定会去垃圾回收
     */
    @Test
    public  void test2() {
        LocalDateTime start = LocalDateTime.now();


        try (
                FileChannel inChannel = FileChannel.open(
                        //Path path:地址 OpenOption... options:指定打开文件的方式
                        Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\熊出没·奇幻空间 熊出没奇幻空间 蓝光(1080P).qlv"),
                        //只读
                        StandardOpenOption.READ);
                FileChannel outChannel = FileChannel.open(
                        Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\2.qlv"),
                        //CREATE_NEW:如果文件存在就报错,不存在就创建(安全)
                        //CREATE:存在就覆盖
                        StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW)) {

            //通过map(模式,当前位置,总量)方法创建 映射字节缓冲区
            //MappedByteBuffer是DirectByteBuffer的父类 都可以通过这个引用操作那块物理内存
            MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

            byte[] bytes = new byte[inMappedBuffer.limit()];
            //读源文件管道的直接缓冲区的数据写到bytes中
            inMappedBuffer.get(bytes);
            //读bytes中的数据写到目标文件管道的直接缓冲区中
            outMappedBuffer.put(bytes);

        } catch (IOException e) {
            e.printStackTrace();
        }
        System.gc();
        LocalDateTime end = LocalDateTime.now();
        Duration between = Duration.between(start, end);
        System.out.println(between.getSeconds() + "秒");
    }
transferFrom,transferTo 通道运输

还有transferFrom()transferTo()方法直接通道传输,底层帮我们写直接缓冲区

/**
     * 通道传输
     * 使用transferFrom和transferTo直接通道传输,底层帮我们写直接缓冲区
     * 12s
     */
    @Test
    public  void test3() {

        LocalDateTime start = LocalDateTime.now();
        try (
                FileChannel inChannel = FileChannel.open(
                        //Path path:地址 OpenOption... options:指定打开文件的方式
                        Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\熊出没·奇幻空间 熊出没奇幻空间 蓝光(1080P).qlv"),
                        //只读
                        StandardOpenOption.READ);
                FileChannel outChannel = FileChannel.open(
                        Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\2.qlv"),
                        //CREATE_NEW:如果文件存在就报错,不存在就创建(安全)
                        //CREATE:存在就覆盖
                        StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW)) {
            //inChannel.transferTo(long position, long count,WritableByteChannel target):
            // 将inChannel管道中position位置,count长度的字节数据 传送到 目标可写字节管道中
            //inChannel.transferTo(0, inChannel.size(), outChannel);

            //outChannel.transferFrom(ReadableByteChannel src,long position, long count)
            //从可读字节管道中position位置,count长度的字节数据 传送到 此管道中
            //盲猜 方法内部自己使用了直接缓冲区
            outChannel.transferFrom(inChannel,0,inChannel.size());

        } catch (IOException e) {
            e.printStackTrace();
        }
        LocalDateTime end = LocalDateTime.now();
        System.out.println(Duration.between(start, end).getSeconds() + "s");
    }
分散(Scatter)与聚集(Gather)

分散读取: 一个通道中的数据分散读取到成多个缓冲区中(按照顺序)

聚集写入: 把多个缓冲区的数据聚集写入到一个通道中 (按照顺序)

实际就是用通道的read(),wirte()的重载方法,放进去一个缓冲区数组

public final long read(ByteBuffer[] dsts) throws IOException {
        return read(dsts, 0, dsts.length);
}

public final long write(ByteBuffer[] srcs) throws IOException {
        return write(srcs, 0, srcs.length);
}

如果是图片的话,不太建议这种方式,只有当第一个缓冲区比图片大时才能运输成功图片,否则打开的不是原来的图片了

	@Test
    public  void test() {
        try (
                //RandomAccessFile(String name文件位置, String mode模式:读,读写等)
                RandomAccessFile accessInFile = new RandomAccessFile("D:\\Tc.l\\学习\\依赖.txt", "rw");
                FileChannel channel1 = accessInFile.getChannel();
                RandomAccessFile accessOutFile = new RandomAccessFile("D:\\Tc.l\\学习\\1.txt", "rw");
                FileChannel channel2 = accessOutFile.getChannel();
        ) {
            ByteBuffer buffer1 = ByteBuffer.allocate(100);
            ByteBuffer buffer2 = ByteBuffer.allocate(1024);
            ByteBuffer[] buffers = {buffer1,buffer2};
            //把通道1中的数据 写到多个缓冲区中(按顺序,写满了就换下一个)
            channel1.read(buffers);
            //打印多个缓冲区中的数据
            for (ByteBuffer buffer : buffers) {
                System.out.println(new String(buffer.array(),0,buffer.limit()));
            }

            //把每一个缓冲区都切换成读模式
            for (ByteBuffer buffer : buffers) {
                buffer.flip();
            }
            //把多个缓冲区中的数据 写到 通道2中
            channel2.write(buffers);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
字符集

在java.nio.charset.Charset包中

可以拿到编码器,解码器,可以对缓冲区进行编码与解码

	@Test
    public void printAvailableCharsets() {
        //打印所有可用字符集
        SortedMap<String, Charset> map = Charset.availableCharsets();
        Set<Map.Entry<String, Charset>> entrySet = map.entrySet();
        for (Map.Entry<String, Charset> entry : entrySet) {
            System.out.println(entry.getKey() + "===" + entry.getValue());
        }
    }

    @Test
    public void test() throws CharacterCodingException {
        Charset charset = Charset.forName("UTF-8");
        //得到UTF-8编码器
        CharsetEncoder charsetEncoder = charset.newEncoder();

        //得到UTF-8解码器
        CharsetDecoder charsetDecoder = charset.newDecoder();

        CharBuffer buffer = CharBuffer.allocate(1024);
        buffer.put("采用UTF-8编码");
        //切换读模式
        buffer.flip();
        //使用UTF-8编码器进行编码 得到字节缓冲区
        ByteBuffer encodeBuffer = charsetEncoder.encode(buffer);
        for (int i = 0; i < 17; i++) {
            System.out.println(encodeBuffer.get());
        }


        //使用UTF-8解码器对刚刚编的码 进行 解码(事先需要把刚刚编的码切换模式)
        encodeBuffer.flip();
        CharBuffer decodeBuffer = charsetDecoder.decode(encodeBuffer);
        System.out.println(decodeBuffer.toString());

        System.out.println("==================================================");
        //使用GBK解码器对刚刚编的码 进行 解码(发生乱码)
        Charset gbk = Charset.forName("GBK");
        CharsetDecoder gbkDecoder = gbk.newDecoder();

        encodeBuffer.flip();
        CharBuffer gbkDecodeBuffer = gbkDecoder.decode(encodeBuffer);
        System.out.println(gbkDecodeBuffer.toString());

        /*
            采用UTF-8编码
            ==================================================
            閲囩敤UTF-8缂栫爜
        */
    }

阻塞与非阻塞

阻塞

传统IO是阻塞式的,某线程调用读写操作时,会被阻塞直到有数据被读取或写入,在被阻塞期间线程不能执行其他任务

因此在网络通信时,服务端要为每一个客户端提供一个线程来处理读写,当客户端数量多时,性能会下降

在这里插入图片描述

模拟阻塞式网络通信


    @Test
    public void client() {
        try (
                //1.获得通道
                SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
                FileChannel inFileChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ)) {
            //2.指定缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //3.读取本地文件,发送到服务器
            while ((inFileChannel.read(buffer)) != -1) {
                buffer.flip();
                sChannel.write(buffer);
                buffer.clear();
            }

            //相当于告诉服务端输入结束了
            sChannel.shutdownOutput();

            //接受服务器端的反馈
            int len = 0;
            while ((len = sChannel.read(buffer))!=-1){
                buffer.flip();
                System.out.println(new String(buffer.array(),0,len));
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void server() {

        try (
                //1.获得通道
                ServerSocketChannel ssChannel = ServerSocketChannel.open();
                FileChannel outFileChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            //2.绑定连接
            ssChannel.bind(new InetSocketAddress(9898));
            //3.指定缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //4.拿到客户端通道
            SocketChannel sChannel = ssChannel.accept();
            //5.接受客户端数据,保存到本地
            while ((sChannel.read(buffer)) != -1) {
                buffer.flip();
                outFileChannel.write(buffer);
                buffer.clear();
            }
            //6.给客户端响应
            buffer.put("服务端接受数据成功".getBytes());
            buffer.flip();
            sChannel.write(buffer);

            //7.关闭客户端通道
            sChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
非阻塞

Java NIO是非阻塞的,某线程在某通道调用读写操作时,若没有数据被读取或写入,该线程就会执行其他任务(线程通常把非阻塞IO操作的空间时间用在其他通道上执行IO操作)

因此在网络通信时,NIO可以让服务器用一个或多个线程来处理连接服务器端的所有客户端

选择器(Selector)

前面说过选择器是NIO实现非阻塞式的核心

  1. 把客户端通道注册到选择器中

  2. 选择器监控这些客户端通道的状况

  3. 只有某个通道"准备就绪"了,选择器才把它发送到服务端的一个或多个线程上

在这里插入图片描述

SelectorSelectableChannel对象的多路复用器

Selector可以同时监控多个SelectableChannel对象(利用Selector可以让一个线程管理多个通道)

  • SelectableChannel结构
    在这里插入图片描述

    • 使用Selector.open()方法获得选择器

      Selector selector = Selector.open();
      
    • 使用SelectableChannel.register(Selector sel,int ops)方法来将通道注册到此选择器上

      //获得通道
      ServerSocketChannel ssChannel = ServerSocketChannel.open()
      //切换为非阻塞模式    
      ssChannel.configureBlocking(false);
      //register(Selector sel[注册到哪个选择器上], int ops[监听事件类型])
      ssChannel.register(selector, SelectionKey.OP_ACCEPT);
      
  • SelectionKey表示SelectableChannelSelector之间的注册关系

    • 监听事件类型可用SelectionKey的四个常量表示
    1. SelectionKey.OP_READ = 1 << 0
    2. SelectionKey.OP_WRITE = 1 << 2
    3. SelectionKey.OP_CONNECT = 1 << 3连接
    4. SelectionKey.OP_ACCEPT = 1 << 4接收

    如果注册时不止一个监听事件可以用逻辑位或|连接

SocketChannel

SocketChannel是一个连接到TCP网络套接字的通道

模拟非阻塞式网络通信

	@Test
    public void client() {
        try (//1.得到通道
             SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9890))) {
            //2.切换成非阻塞式
            sChannel.configureBlocking(false);
            //3.创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //4.发送文件
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            String s = now.format(formatter);

            buffer.put(s.getBytes());
            buffer.flip();
            sChannel.write(buffer);
            buffer.clear();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void server() {

        try (   //1.获得通道
                ServerSocketChannel ssChannel = ServerSocketChannel.open()) {
            //2.切换成非阻塞式
            ssChannel.configureBlocking(false);
            //3.绑定连接
            ssChannel.bind(new InetSocketAddress(9890));
            //4.获取选择器
            Selector selector = Selector.open();
            //5.把通道注册到选择器上,并指定监听事件
            //register(Selector sel[注册到哪个选择器上], int ops[SelectionKey属性常数])
            ssChannel.register(selector, SelectionKey.OP_ACCEPT);

            //6.轮询式获取选择器上已经准备就绪的事件
            while (selector.select() > 0) {
                //7.获得当前选择器中所有注册的 选择键(就绪的监听事件)
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

                while (iterator.hasNext()) {
                    //8.获取准备就绪的事件
                    SelectionKey key = iterator.next();

                    //9.判断具体是什么事件准备就绪
                    if (key.isAcceptable()) {
                        //10.接受就绪事件 获得客户端
                        SocketChannel sChannel = ssChannel.accept();
                        //11.切换非阻塞式
                        sChannel.configureBlocking(false);
                        //12.把该通道注册到选择器上
                        sChannel.register(selector, SelectionKey.OP_READ);
                    } else if (key.isReadable()) {
                        //13. 获得当前选择器上"读就绪"的通道
                        SocketChannel sChannel = (SocketChannel) key.channel();

                        //14.读取数据
                        ByteBuffer buffer = ByteBuffer.allocate(1024);

                        int len = 0;
                        while ((len = sChannel.read(buffer)) != -1) {
                            buffer.flip();
                            System.out.println(new String(buffer.array(), 0, len));
                            buffer.clear();
                        }
                    }

                    //15.取消选择键
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
DetagramChannel

DatagramChannel是一个能收发UDP包的通道

	@Test
    public void client() {
        try (
                DatagramChannel dc = DatagramChannel.open()) {
            dc.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            LocalDateTime now = LocalDateTime.now();
            buffer.put(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).getBytes());
            buffer.flip();
            dc.send(buffer, new InetSocketAddress("127.0.0.1", 8090));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void server() {
        try (DatagramChannel dc = DatagramChannel.open()) {
            dc.configureBlocking(false);
            Selector selector = Selector.open();
            dc.bind(new InetSocketAddress(8090));
            dc.register(selector, SelectionKey.OP_READ);
            while(selector.select()>0){
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    if (key.isReadable()) {
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        dc.receive(buffer);
                        buffer.flip();
                        System.out.println(new String(buffer.array(),0,buffer.limit()));
                        buffer.clear();
                    }
                }
                iterator.remove();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
Pipe

Pipe是管道,用于2个线程间单向数据连接

从Pipe的sink通道写入,从source通道读取

在这里插入图片描述

	@Test
    public void test1(){
        try {
            Pipe pipe = Pipe.open();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //发送数据
            Pipe.SinkChannel sinkChannel = pipe.sink();
            buffer.put(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss")).getBytes());
            buffer.flip();
            sinkChannel.write(buffer);

            //接受数据
            Pipe.SourceChannel sourceChannel = pipe.source();
            ByteBuffer allocate = ByteBuffer.allocate(1024);
            int len = sourceChannel.read(allocate);
            allocate.flip();
            System.out.println(new String(allocate.array(),0,len));

            //关闭
            sinkChannel.close();
            sourceChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

学习视频:尚硅谷NIO
如有错误,麻烦指出,感谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值