• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

2.BIO和NIO区别

武飞扬头像
PacosonSWJTU
帮助1

【README】

  • 1.本文总结自B站《netty-尚硅谷》,很不错;
  • 2.本文介绍 BIO, NIO的知识;

【1】BIO(传统java IO模型)

1)BIO-Blocking IO:同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理;

2)原理图如下:

学新通

【图解】

  • 1.client: 可以使用 telnet 作为客户端连接服务器;
  • 2.BIO:每当有一个客户端连接时,服务器就启动一个新线程与之通讯(当然可以复用线程池)

 【代码实现】

  1.  
    /**
  2.  
    * @Description 阻塞式IO服务器
  3.  
    * @author xiao tang
  4.  
    * @version 1.0.0
  5.  
    * @createTime 2022年08月13日
  6.  
    */
  7.  
    public class BIOServer {
  8.  
    public static void main(String[] args) throws IOException {
  9.  
    // 创建一个线程池
  10.  
    ExecutorService threadPool = Executors.newCachedThreadPool();
  11.  
    int order = 0;
  12.  
     
  13.  
    // 创建 服务器 套接字
  14.  
    ServerSocket serverSocket = new ServerSocket(6666);
  15.  
    System.out.println("服务器启动成功.");
  16.  
    while(true) {
  17.  
    System.out.println("等待客户端请求");
  18.  
    Socket socket = serverSocket.accept(); // 没有客户端请求,accept阻塞
  19.  
    System.out.printf("客户端[%d]请求建立连接\n", order);
  20.  
    final int orderCopy = order;
  21.  
    threadPool.execute(()->{
  22.  
    handler(socket, orderCopy);
  23.  
    });
  24.  
    }
  25.  
    }
  26.  
     
  27.  
    /**
  28.  
    * @description 与客户端通讯
  29.  
    * @param socket 连接套接字
  30.  
    * @author xiao tang
  31.  
    * @date 2022/8/13
  32.  
    */
  33.  
    public static void handler(Socket socket, int order) {
  34.  
    byte[] byteArr = new byte[1024];
  35.  
     
  36.  
    try {
  37.  
    // 读取客户端发送的数据
  38.  
    InputStream inputStream = socket.getInputStream();
  39.  
    int length = 0;
  40.  
    // 客户端没有数据,read阻塞
  41.  
    // while( ( length = inputStream.read(byteArr))!= -1) {
  42.  
    // System.out.println(new String(byteArr, 0, length));
  43.  
    // }
  44.  
    while(true) {
  45.  
    System.out.printf("线程id[%s],等待客户端[%d]发送数据\n", Thread.currentThread().getId(), order);
  46.  
    length = inputStream.read(byteArr);
  47.  
    if (length == -1) break;
  48.  
    System.out.printf("线程id[%s],客户端[%d]:" new String(byteArr, 0, length) "\n", Thread.currentThread().getId(), order);
  49.  
    }
  50.  
    } catch (IOException e) {
  51.  
    e.printStackTrace();
  52.  
    } finally {
  53.  
    // 关闭与客户端的连接
  54.  
    try {
  55.  
    socket.close();
  56.  
    System.out.printf("关闭与客户端[%d]的连接\n", order);
  57.  
    } catch (IOException e) {
  58.  
    }
  59.  
    }
  60.  
    }
  61.  
    }
学新通

【2】NIO (同步非阻塞IO)

【2.0】NIO概述

  • 1)Java NIO 全称 java non-blocking IO,是指 JDK 提供的新API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的;
  • 2)NIO 相关类都被放在 java.nio 包及子包下,并且对原java.io 包中的很多类进行改写。
  • 3)NIO 有三大核心部分: Channel(通道),Buffer(缓冲区), Selector(选择器)
  • 4)NIO是 面向缓冲区 ,或者面向块编程的。数据读取到缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络;

5)NIO非阻塞IO处理架构:

学新通

【图解】

  • 1.使用NIO,服务器启动一个线程可以处理多个客户端请求。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。
  • 2.通道Channel 是封装在  java输入输出流中的对象;

【小结】

  • NIO 有三大核心部分: Channel(通道),Buffer(缓冲区), Selector(选择器)

【比较】NIO 与 BIO的比较

  • 1)BIO 以流的方式处理数据,而 NIO 以块的方式处理数据, 块I/O 的效率比流I/O高很多
  • 2) BIO 是阻塞的,NIO 则是非阻塞的;
  • 3) BIO基于字节流和字符流进行操作,而 NIO 基于Channel(通道)和Buffer(缓冲区)进行操作;磁盘数据借助通道读入到缓冲区,或者从缓冲区写出到磁盘。

补充:NIO中,Selector(选择器) 用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道;


【3】NIO3大核心部分

1)NIO 有三大核心部分: Buffer(缓冲区),  Channel(通道),Selector(选择器) ;

 2)3个模块关联关系如下:

学新通

【图解】

  • 1)一个服务器线程对应一个选择器;一个选择器对应多个通道(因为多个通道可以注册到同一个选择器);
  • 2)一个通道对应一个缓冲区 buffer;
  • 4)cpu切换到哪个channel运行 ,是由事件决定的;事件 event 是一个很重要的概念(如 ACCEPT, READ 事件);
  • 5)Selector 会根据不同事件,在各个通道上切换;
  • 6)Buffer 本质上就是一个内存块,底层是由一个数组实现;
  • 7)数据读取或写入,是通过buffer来完成,这个和 BIO 是有本质不同的;此外,BIO要么是输入流,要么是输出流,不能双向;但NIO的buffer是可读可写的,需要flip方法进行切换
  • 8)Channel 是双向的,可以反应底层操作系统的情况,如 linux底层的操作系统通道就是双向的;

【3.1】NIO中的Buffer缓冲 

【3.1.1】buffer缓冲概述

1)缓冲区(Buffer):

  • 缓冲区本质上是一个可以读写数据的内存块(如字节数组),可以更轻松地使用内存块;
  • 缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。

2)Channel 提供读写文件的接口、都必须经由Buffer,如下图所示。

学新通

参见 FileChannel读写文件代码( NIOFileChannel015.java ,下文有)

【代码】NIO中缓冲区的使用

  1.  
    /**
  2.  
    * @Description 缓冲区使用
  3.  
    * @author xiao tang
  4.  
    * @version 1.0.0
  5.  
    * @createTime 2022年08月13日
  6.  
    */
  7.  
    public class BasicBuffer {
  8.  
     
  9.  
    /**
  10.  
    * @description Buffer的使用
  11.  
    */
  12.  
    public static void main(String[] args) {
  13.  
    // 创建一个buffer
  14.  
    IntBuffer intBuffer = IntBuffer.allocate(5);
  15.  
    // 向buffer存放数据
  16.  
    for (int i = 0; i < intBuffer.capacity(); i ) {
  17.  
    intBuffer.put(i*2);
  18.  
    }
  19.  
    // 从buffer 读取数据
  20.  
    // 需要先把 buffer 转换,读写切换(写模式切换到读模式)
  21.  
    intBuffer.flip();
  22.  
     
  23.  
    intBuffer.position(1);
  24.  
    while(intBuffer.hasRemaining()) {
  25.  
    System.out.println(intBuffer.get());
  26.  
    }
  27.  
     
  28.  
    }
  29.  
    }
学新通

【3.1.2】buffer子类及属性

1)子类

学新通

【图解】

  • ByteBuffer,存储字节数据到缓冲区(常用)-字节缓冲区
  • ShortBuffer,存储短整型数据到缓冲区;
  • CharBuffer,存储字符数据到缓冲区;
  • IntBuffer,存储整数数据到缓冲区;
  • LongBuffer,存储长整型数据到缓冲区;
  • DoubleBuffer,存储双精度到缓冲区;
  • FloatBuffer,存储单精度到缓冲区;

2)buffer的4个属性

  1.  
    public abstract class Buffer {
  2.  
    private int mark = -1;
  3.  
    private int position = 0;
  4.  
    private int limit;
  5.  
    private int capacity;

序号

属性

描述

1

Capacity

容量,即缓冲区大小,定长不能改变。

2

Position

当前位置;表示下一个要被读写的元素的下标(索引)

3

limit

表示缓冲区最大可用位置,读写时不能超过极限位置

4

Mark

标记,不修改。

【3.1.3】只读buffer

  1.  
    /**
  2.  
    * @Description 只读 buffer
  3.  
    * @author xiao tang
  4.  
    * @version 1.0.0
  5.  
    * @createTime 2022年08月16日
  6.  
    */
  7.  
    public class NIOReadOnly017 {
  8.  
    public static void main(String[] args) {
  9.  
    ByteBuffer buffer = ByteBuffer.allocate(64);
  10.  
     
  11.  
    for (int i = 0; i < 4; i ) {
  12.  
    buffer.put((byte) i);
  13.  
    }
  14.  
    // 读取
  15.  
    buffer.flip();
  16.  
    // 得到一个只读buffer
  17.  
    ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
  18.  
    System.out.println(readOnlyBuffer.getClass()); // java.nio.HeapByteBufferR
  19.  
     
  20.  
    // 读取
  21.  
    while (readOnlyBuffer.hasRemaining()) {
  22.  
    System.out.println(readOnlyBuffer.get());
  23.  
    }
  24.  
    readOnlyBuffer.flip();
  25.  
    readOnlyBuffer.put((byte)0); // 抛出异常-ReadOnlyBufferException
  26.  
    }
  27.  
    }
学新通

【3.1.4】 get与put() 方法操作 数据类型要一致

buffer缓冲中 get与put() 方法操作 数据类型要一致 :

  1.  
    /**
  2.  
    * @Description buffer put 与 get 操作的数据类型要一致
  3.  
    * @author xiao tang
  4.  
    * @version 1.0.0
  5.  
    * @createTime 2022年08月16日
  6.  
    */
  7.  
    public class NIOByteBufferPugGet017 {
  8.  
    public static void main(String[] args) {
  9.  
    ByteBuffer buffer = ByteBuffer.allocate(64);
  10.  
     
  11.  
    // 类型化方式放入数据
  12.  
    buffer.putInt(100);
  13.  
    buffer.putLong(4);
  14.  
    buffer.putChar('中');
  15.  
    buffer.putShort((short)4);
  16.  
     
  17.  
    // 取出
  18.  
    buffer.flip();
  19.  
     
  20.  
    System.out.println();
  21.  
    System.out.println(buffer.getInt());
  22.  
    System.out.println(buffer.getChar());
  23.  
    System.out.println(buffer.getLong());
  24.  
    System.out.println(buffer.getLong()); // 抛出异常
  25.  
    }
  26.  
    }
学新通

【3.1.5】 Buffer的分散与聚集(buffer数组读写数据)

前面我们讲的读写操作,都是通过一个Buffer 完成的,NIO还支持通过多个Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和Gathering;

2)buffer的分散与聚集( Scattering 与 Gathering )

  • Scattering: 将数据写入到buffer时,可以采用buffer数组,依次写入。
  • Gathering: 从buffer读取数据时, 可以采用buffer数组,依次读取。
  1.  
    /**
  2.  
    * @Description buffer的分散与聚集( Scattering 与 Gathering )
  3.  
    * Scattering: 将数据写入到buffer时,可以采用buffer数组,依次写入。
  4.  
    * Gathering: 从buffer读取数据时, 可以采用buffer数组,依次读取。
  5.  
    * @author xiao tang
  6.  
    * @version 1.0.0
  7.  
    * @createTime 2022年08月16日
  8.  
    */
  9.  
    public class ScatterAndGatherBuffer019 {
  10.  
    public static void main(String[] args) throws Exception {
  11.  
    // 使用 ServerSocketChannel 和 SocketChannel
  12.  
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  13.  
    InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
  14.  
     
  15.  
    // 绑定端口到socket 并启动
  16.  
    serverSocketChannel.socket().bind(inetSocketAddress);
  17.  
     
  18.  
    // 创建buffer数组
  19.  
    ByteBuffer[] byteBuffers = new ByteBuffer[2];
  20.  
    byteBuffers[0] = ByteBuffer.allocate(5);
  21.  
    byteBuffers[1] = ByteBuffer.allocate(3);
  22.  
    // 等待客户端连接(telnet)
  23.  
    SocketChannel socketChannel = serverSocketChannel.accept();
  24.  
     
  25.  
    // 循环读取
  26.  
    int msgLength = 8; // 假定从客户端接收8个字节
  27.  
    int byteRead = 0;
  28.  
    while (byteRead < msgLength) {
  29.  
    long singleLength = socketChannel.read(byteBuffers);
  30.  
    byteRead = singleLength;
  31.  
    System.out.println("byteRead = " byteRead);
  32.  
    // 流打印,查看当前buffer的position 和 limit
  33.  
    Arrays.asList(byteBuffers).stream()
  34.  
    .map(buffer -> "position = " buffer.position() ", limit=" buffer.limit()).forEach(System.out::println);
  35.  
    }
  36.  
    // 将所有buffer反转-flip
  37.  
    Arrays.asList(byteBuffers).forEach(x -> x.flip());
  38.  
    // 将数据读出显示到客户端
  39.  
    long byteWrite = 0;
  40.  
    while (byteWrite < msgLength) {
  41.  
    long length = socketChannel.write(byteBuffers);
  42.  
    byteWrite = length;
  43.  
    }
  44.  
    // 将所有buffer 清空
  45.  
    Arrays.asList(byteBuffers).forEach(x -> x.clear());
  46.  
    System.out.println("byteRead = " byteRead ", byteWrite = " byteWrite);
  47.  
    }
  48.  
    }
学新通

 【3.2】channel 通道的代码示例

1)写出到文件

  1.  
    /**
  2.  
    * @Description nio文件通道读文件
  3.  
    * @author xiao tang
  4.  
    * @version 1.0.0
  5.  
    * @createTime 2022年08月16日
  6.  
    */
  7.  
    public class NIOFileChannel013 {
  8.  
    public static void main(String[] args) throws Exception {
  9.  
    String text = "hello 世界";
  10.  
    // 创建一个输出流
  11.  
    try (FileOutputStream fos = new FileOutputStream(
  12.  
    new File("D://temp/netty/nio_file_channel01.txt"))) {
  13.  
    // 获取对应 FileChannel,FileChannel 是抽象类,具体类型是FileChannelImpl
  14.  
    FileChannel fileChannel = fos.getChannel();
  15.  
    // 创建一个缓冲区
  16.  
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  17.  
    // 把文本写入 ByteBuffer, 并把 ByteBuffer 反转-flip
  18.  
    byteBuffer.put(text.getBytes(StandardCharsets.UTF_8));
  19.  
    byteBuffer.flip();
  20.  
    // 把 byteBuffer 写入到 FileChannel
  21.  
    fileChannel.write(byteBuffer);
  22.  
    } catch (Exception e) {
  23.  
    System.out.println("异常");
  24.  
    throw e;
  25.  
    }
  26.  
    }
  27.  
    }
学新通

2)从文件读入数据

  1.  
    /**
  2.  
    * @Description FileChannel读文件
  3.  
    * @author xiao tang
  4.  
    * @version 1.0.0
  5.  
    * @createTime 2022年08月16日
  6.  
    */
  7.  
    public class NIOFileChannel014 {
  8.  
    public static void main(String[] args) throws Exception {
  9.  
    // 创建一个输入流
  10.  
    try (FileInputStream fis = new FileInputStream(
  11.  
    new File("D://temp/netty/nio_file_channel01.txt"))) {
  12.  
    // 获取对应 FileChannel,FileChannel 是抽象类,具体类型是FileChannelImpl
  13.  
    FileChannel fileChannel = fis.getChannel();
  14.  
    // 创建一个缓冲区
  15.  
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  16.  
    // 从文件读入内容到缓冲区
  17.  
    fileChannel.read(byteBuffer);
  18.  
    // 显示
  19.  
    System.out.println(new String(byteBuffer.array(), StandardCharsets.UTF_8));
  20.  
    } catch (Exception e) {
  21.  
    System.out.println("异常");
  22.  
    throw e;
  23.  
    }
  24.  
    }
  25.  
    }
学新通

3)读写文件(拷贝)

  1.  
    /**
  2.  
    * @Description FileChannel读写文件
  3.  
    * @author xiao tang
  4.  
    * @version 1.0.0
  5.  
    * @createTime 2022年08月16日
  6.  
    */
  7.  
    public class NIOFileChannel015 {
  8.  
    public static void main(String[] args) throws Exception {
  9.  
    // 创建一个输入流
  10.  
    try (FileInputStream fis = new FileInputStream("D://temp/netty/nio_file_channel01.txt");
  11.  
    FileOutputStream fos = new FileOutputStream("D://temp/netty/nio_file_channel02.txt")) {
  12.  
    // 获取对应 FileChannel
  13.  
    FileChannel fileChannel01 = fis.getChannel();
  14.  
    FileChannel fileChannel02 = fos.getChannel();
  15.  
     
  16.  
    // 创建一个缓冲区
  17.  
    ByteBuffer byteBuffer = ByteBuffer.allocate(4);
  18.  
    // 循环读取
  19.  
    while(fileChannel01.read(byteBuffer) != -1) {
  20.  
    // 反转
  21.  
    byteBuffer.flip();
  22.  
    // 将buffer 中的数据写入到 02.txt
  23.  
    fileChannel02.write(byteBuffer);
  24.  
    // 清空 buffer
  25.  
    byteBuffer.clear();
  26.  
    }
  27.  
    // 显示
  28.  
    System.out.println("拷贝成功.");
  29.  
    } catch (Exception e) {
  30.  
    System.out.println("异常");
  31.  
    throw e;
  32.  
    }
  33.  
    }
  34.  
    }
学新通

补充: Buffer.flip() 切换读写模式(如写模式切换为读模式)

学新通

4)用  transferFrom 拷贝文件

  1.  
    /**
  2.  
    * @Description 采用 transferFrom 拷贝图片
  3.  
    * @author xiao tang
  4.  
    * @version 1.0.0
  5.  
    * @createTime 2022年08月16日
  6.  
    */
  7.  
    public class NIOFileChannel016 {
  8.  
    public static void main(String[] args) throws Exception {
  9.  
    // 创建一个输入流
  10.  
    try (FileInputStream fis = new FileInputStream("D://temp/netty/image/1.jpg");
  11.  
    FileOutputStream fos = new FileOutputStream("D://temp/netty/image/2.jpg")) {
  12.  
    // 获取对应 FileChannel
  13.  
    FileChannel srcChannel = fis.getChannel();
  14.  
    FileChannel destChannel = fos.getChannel();
  15.  
     
  16.  
    // 创建一个缓冲区
  17.  
    ByteBuffer byteBuffer = ByteBuffer.allocate(4);
  18.  
    // 使用 transferFrom 完成拷贝
  19.  
    destChannel.transferFrom(srcChannel, 0, srcChannel.size());
  20.  
    // 显示
  21.  
    System.out.println("拷贝成功.");
  22.  
    } catch (Exception e) {
  23.  
    System.out.println("异常");
  24.  
    throw e;
  25.  
    }
  26.  
    }
  27.  
    }
学新通

【3.3】选择器(Selector)

refer2

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgakihg
系列文章
更多 icon
同类精品
更多 icon
继续加载