区块链技术博客
www.b2bchain.cn

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

这篇文章主要介绍了一篇通俗易懂的文章初探NIO的讲解,通过具体代码实例进行16457 讲解,并且分析了一篇通俗易懂的文章初探NIO的详细步骤与相关技巧,需要的朋友可以参考下https://www.b2bchain.cn/?p=16457

本文实例讲述了2、树莓派设置连接WiFi,开启VNC等等的讲解。分享给大家供大家参考文章查询地址https://www.b2bchain.cn/7039.html。具体如下:

Java NIO

文章目录

  • Java NIO
      • 简介
        • 什么是Java NIO
        • NIO与IO的区别
      • 通道与缓冲区
        • 缓冲区(Buffer)
          • 缓冲区的结构
          • 缓冲区的字段(属性)
          • 缓冲区的方法
          • 深入了解缓冲区的属性与方法
        • 通道(Channel)
          • 通道的结构
          • 通道的方法
          • 深入了解非直接与直接缓冲区的区别
            • 非直接与直接缓冲区+通道运输
            • transferFrom,transferTo 通道运输
            • 分散(Scatter)与聚集(Gather)
        • 字符集
      • 阻塞与非阻塞
        • 阻塞
        • 非阻塞
          • 选择器(Selector)
            • SocketChannel
            • DetagramChannel
            • Pipe

简介

什么是Java NIO

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

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

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

NIO与IO的区别

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

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

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

通道与缓冲区

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传一篇通俗易懂的文章初探NIO

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

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

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

缓冲区(Buffer)

缓冲区的结构

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

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

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

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

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

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

  • 非直接缓冲区

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

  • 直接缓冲区

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

非直接缓冲区:读取物理磁盘中的某个文件时,会把读到的数据放在内核地址中,然后经过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是阻塞式的,某线程调用读写操作时,会被阻塞直到有数据被读取或写入,在被阻塞期间线程不能执行其他任务

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

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

模拟阻塞式网络通信

     @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. 只有某个通道"准备就绪"了,选择器才把它发送到服务端的一个或多个线程上

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

SelectorSelectableChannel对象的多路复用器

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

  • SelectableChannel结构
    一篇通俗易懂的文章初探NIO

    • 使用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通道读取

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

	@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
如有错误,麻烦指出,感谢~

本文转自互联网,侵权联系删除一篇通俗易懂的文章初探NIO

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 一篇通俗易懂的文章初探NIO
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们