JAVA NIO基础知识及应用

概述

NIO是Java中一种新的IO,API,提供了与传统IO不同的操作,NIO是一种同步非阻塞、基于事件的IO API

核心组件

  • Buffer:代表NIO中数据读、写的中转池,无论是收到写数据还是读数据,都会通过Buffer
  • Channel:代表数据的源头或目的源,为Buffer提供(写)数据和从Buffer获取(读)数据
  • Selector:用于监听一个或多个通道(Channel)的事件,当通道发生事件时,选择器可以对被发生事件的通道进行事件处理,例如像通道被连接、通道有可读/写数据等

关系图

在这里插入图片描述

Buffer缓冲区组件

概述:这个缓冲区主要是储存通道所使用(用来读、写)的数据,这个通道的基本接口是Channel,其实现这个接口的实现类有基于各种基本类型的Buffer,例如IntBuffer

重要的属性
  • capacity:这个缓冲区的最大大小
  • limit:这个缓冲区的可访问到的最大位置,即极限距离,这个值应该小于等于capacity
  • position:这个缓冲区当前读/写的位置,例如如果position为5则获取position缓冲区中第5个元素
  • mark:一个标记,用于reset()时恢复位置的标记

Channel通道组件

概述:这个组件可以理解为与客户的连接,一个连接就是一个通道,客户通过通道写消息到缓冲区后再将缓冲区(如果客户端有用到缓冲区)数据写入通道,服务器通过通道读消息到缓冲区,再从缓冲区得到具体数据,这就是通道的一个工作流程

通道类型

  • FileChannel:用于文件读/写的通道,可以通过FileInputStream、FileOutputStream、RandomAccessFile的getChannel()方法得到可读/写的通道
  • DataChannel:通过UDP读写网络数据的通道
  • SocketChannel:通过TCP读写网络数据的通道
  • ServerSocketChannel:可监听TCP连接,当有TCP客户端请求连接且连接成功时,它对每一个连接都产生一个SocketChannel通道
  • 基于通道的写文件案例
import sun.nio.ch.FileChannelImpl;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;

public class Demo1 {
  public static void main(String[] args) throws Exception {
    String filename = "C:\\Users\\Administrator\\Desktop\\file1.txt";
    FileOutputStream fileOutputStream = new FileOutputStream(filename);
    FileChannel fileChannel = fileOutputStream.getChannel();
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    byteBuffer.put("杀戮都市:O".getBytes());
    byteBuffer.flip();
    fileChannel.write(byteBuffer);
    fileOutputStream.close();
  }
}
  • 基于通道的读文件案例
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;

public class Demo2 {
  public static void main(String[] args) throws Exception {
    String filename = "C:\\Users\\Administrator\\Desktop\\file1.txt";
    FileInputStream fileInputStream = new FileInputStream(filename);
    FileChannel fileChannel = fileInputStream.getChannel();
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    fileChannel.read(byteBuffer);
    byteBuffer.flip();
    String str = new String(byteBuffer.array(), 0, byteBuffer.limit(), Charset.forName("UTF-8"));
    System.out.println(str);
  }
}

选择器

概述:异步IO的核心类,允许一个选择器线程管理&处理多个通道,当通道发送事件时会被选择器的select()捕获,此时我们对发生事件的通道进行处理

基于TCP的单线程多用户案例

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Set;

public class Demo3 {
  public static void main(String[] args) throws Exception {
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    // 必须设置为非阻塞才能被注册上选择器
    Selector selector = Selector.open();
    // 将TCP socket绑定在指定端口上
    serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 9000));
    serverSocketChannel.configureBlocking(false);
    // 将这个TCP通道和有客户准备连接事件绑定并注册在选择器上
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    while (true) {
      // 循环监听选择器是否有事件发生
      while (selector.select(5000) == 0) {
        System.out.println("5s 过去了在选择器上注册的通道没有任何一通道有绑定事件发生");
        continue;
      }
      // 得到有事件发生的这些事件
      Set<SelectionKey> be_selected_key = selector.selectedKeys();
      be_selected_key.forEach((key) -> {
        // 迭代这些发生了事件的Key,并对这些事件作出反应
        if (key.isAcceptable()) {
          try {
            // 如果发送事件是客户准备连接的事件,我们就调用服务器accept方法接收连接
            SocketChannel connected_client = serverSocketChannel.accept();
            // 与客户连接的通道一定也要是非阻塞的
            connected_client.configureBlocking(false);
            // 得到与连接用户关联的这个通道并注册到选择器中,并绑定读事件,和一个Buffer缓冲区
            connected_client.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
            System.out.println("与一个客户取得连接");
          } catch (IOException e) {
            System.err.println("无法与客户进行连接...");
          }
        }

        // 如果发生的是读取事件,也就代表某个客户发送消息到此,准备读数据
        if(key.isReadable()){
          // 已知我们在这个选择器中注册读事件的全是SocketChannel类型通道,所以直接强转
          SocketChannel socket_channel = (SocketChannel) key.channel();
          // 对客户发送的消息我们进行回显
          // key的attachment()方法返回的是注册时的第三个参数对象,如果没有使用第三个参数,那么应该是NUll
          ByteBuffer buffer = (ByteBuffer) key.attachment();
          if(buffer == null){
            buffer = ByteBuffer.allocate(1024);
            buffer.flip(); // 翻转为读模式,因为刚创建默认为写模式
          }
          try {
            // 将通道对应缓冲区初始化
            buffer.clear();
            socket_channel.read(buffer);
            buffer.flip();
            // 反转这个buffer,使其作为写模式,且缓冲区capacity、limit初始为position位置,position归0
            String message = new String(buffer.array(), 0,buffer.limit());
            // 我们将指定缓冲区的内容写入管道中
            System.out.println("收到客户端消息... msg:" + message);
            buffer = ByteBuffer.allocate(1024);
            buffer.put((message + "   ---收到你的消息了哦").getBytes("UTF-8"));
            buffer.flip(); // 设置limit和capacity为position,且posistion=0
            socket_channel.write(buffer); // 发送回显给客户端
          }catch (Exception e){
            System.err.println("用户主动断开连接");
            // 当用户断开连接时我们必须将这个通道从selector中移除
            try {
              key.channel().close(); // 关闭通道
            } catch (IOException ex) {
              System.err.println("无法关闭通道...");
            }
            key.cancel(); // 从selector中移除这个事件key,和对应的通道信息
          }
        }

        /* 移除在已触发事件Key集合中的这个事件Key,移除这个Key不代表清除了Selector与通道的信息,而只是
         * 在"已触发事件Key集合中移除Key"以免重复遍历事件,我们可以理解为selectedKeys()每次不会重新拿
         * select()也不会重新计数,而这些方法是追加的方式进行的,所以确保每次遍历时selectedKeys()为空
         */
        be_selected_key.remove(key);
      });
    }
  }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值