BIO、NIO和AIO

一.引言

何为IO

涉及计算机核心(CPU和内存)与其他设备间数据迁移的过程,就是I/O。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出 I/O 描述了计算机系统与外部设备之间通信的过程。

  • 磁盘I/O
    • 输入:就是从磁盘读取数据到内存
    • 输出:将内存中的数据写入磁盘
  • 网络I/O
    • 输入:从网络中的另一台计算机或服务器获取数据,并将其加载到本地内存中
    • 输出:将本地内存中的数据发送到网络中的其他计算机或服务器

IO的过程

根据大学里学到的操作系统相关的知识:为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为 用户空间(User space)内核空间(Kernel space )

像我们平常运行的应用程序都是运行在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如文件管理、进程通信、内存管理等等,因为这些都是比较危险的操作,不可以由应用程序乱来,只能交给底层操作系统来。也就是说,我们想要进行 IO 操作,只能发起系统调用请求操作系统来间接访问内核空间

我们在平常开发过程中接触最多的就是 磁盘 IO(读写文件)网络 IO(网络请求和响应)

应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用)操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的

当应用程序发起 I/O 调用后,会经历两个步骤(IO执行):

  1. 数据准备:内核等待 I/O 设备准备好数据即操作系统将外部数据加载到内核缓冲区
  2. 数据拷贝:内核将数据从内核缓冲区拷贝到用户进程缓冲区

Java的3种网络IO模型

Java中提供的IO有关的API,也是依赖操作系统层面的IO操作实现的。在Java中,主要有三种IO模型,分别是阻塞IO(BIO)、非阻塞IO(NIO)和 异步IO(AIO)。

可以把Java中的BIO、NIO和AIO理解为是Java语言对操作系统的5种IO模型的封装(在Linux(UNIX)操作系统中,共有五种IO模型,分别是:阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型以及异步IO模型)。程序员在使用这些API的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。

阻塞和非阻塞IO

上面已经说过,应用程序的IO实际是分为两个步骤,IO调用和IO执行。IO调用是由进程发起,IO执行是操作系统的工作。操作系统的IO情况决定了进程IO调用是否能够得到立即响应。

  • 阻塞IO:如果操作系统尚未准备好数据,当前进程或线程一直等待直到其就绪
  • 非阻塞IO:如果操作系统尚未准备好数据,进程或线程并不一直等待其就绪,而是可以做其他事情。进程/线程会周期性地轮询或查询IO操作的状态,以确定数据是否就绪。

非阻塞IO需要进程/线程自己负责查询IO状态;而阻塞IO则是操作系统负责在数据就绪时唤醒进程/线程。

异步和同步IO

  • 同步IO:同步IO是指程序发起IO操作后,程序会一直等待直到IO操作完成,然后再继续执行后续的代码。
  • 异步IO:异步IO是指程序发起IO操作后,它可以继续执行其他任务,而不必等待IO操作完成。当IO操作完成后,程序会得到通知,可以处理已完成的IO操作。异步IO可以提高程序的并发性和响应性,因为它允许程序在等待IO的同时执行其他任务。

自己的理解:我感觉阻塞和非阻塞IO针对的是操作系统未准备好数据时进程的处理方式,是等待还是不等待。异步和同步IO针对的是IO操作未完成时(IO操作包括数据准备和数据拷贝两步骤)进程的处理方式,是等待还是不等待。

二.BIO

  • Java BIO 就是传统的 java io 编程,其相关的类和接口在 java.io
  • BIO(blocking I/O) :同步阻塞 IO 模型 ,即在读写数据过程中会发生阻塞现象,直至
    有可供读取的数据或者数据能够写入。
  • 服务器实现模式为 一个连接一个线程,即客户端有连接请求时服务器端就需 要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器)

映射到Linux操作系统中,这就是一种最简单的IO模型,即阻塞IO。 阻塞 I/O 是最简单的 I/O 模型,一般表现为进程或线程等待某个条件,如果条件不满足,则一直等下去。条件满足,则进行下一步操作。

BIO客户端、服务端通信实现  

Server 服务端

/**
    目标:实现服务端可以同时接收多个客户端的Socket通信需求。
    思路:是服务端每接收到一个客户端socket请求对象之后都交给一个独立的线程来处理客户端的数据交互需求。
 */
public class Server {
    public static void main(String[] args) {
        try {
            // 1、注册端口
            ServerSocket ss = new ServerSocket(9999);
            // 2、定义一个死循环,负责不断的接收客户端的Socket链接请求
            while(true){
                Socket socket = ss.accept();
                // 3、创建一个独立的线程来处理与这个客户端的socket通信需求。
                new ServerThreadReader(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ServerThreadReader 服务端与客户端保持通信的线程

public class ServerThreadReader extends Thread {
    private Socket socket;
    public ServerThreadReader(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 从socket对象中得到一个字节输入流
            InputStream is = socket.getInputStream();
            // 使用缓冲字符输入流包装字节输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while((msg = br.readLine())!=null){
                System.out.println(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Client 客户端

/**
    客户端
 */
public class Client {
    public static void main(String[] args) {
        try {
            // 1、请求与服务端的Socket对象链接
            Socket socket = new Socket("127.0.0.1" , 9999);
            // 2、得到一个打印流
            PrintStream ps = new PrintStream(socket.getOutputStream());
            // 3、使用循环不断的发送消息给服务端接收
            Scanner sc = new Scanner(System.in);
            while(true){
                System.out.print("请说:");
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三.NIO

Java NIO(non-blocking)是从Java 1.4版本开始引入的一个新的IO API,NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行读写操作。

Java NIO(non-blocking) 映射的不是操作系统五大IO模型中的NIO模型(采用轮询的方式检查IO状态),而是另外的一种模型,叫做IO多路复用模型( IO multiplexing )。

IO复用模型核心思路: 系统给我们提供一类函数(如我们耳濡目染的select、 poll、epoll函数),它们可以同时监控多个 fd 的操作,任何一个返回内核数据就绪,应用进程再发起 recvfrom 系统调用。

文件描述符fd(File Descriptor)它是计算机科学中的一个术语,形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符.

目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,目前几乎在所有的操作系统上都有支持。

  • select 调用:内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
  • epoll 调用:属于 select 调用的增强版本,优化了 IO 的执行效率

Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。

在这里插入图片描述

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

1. 三大组件

Channel & Buffer

channel 有一点类似于 流,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 流 要么是输入,要么是输出,channel 比 流 更为底层

常见的 Channel 有

  • FileChannel (文件):从文件中读写数据。
  • DatagramChannel (UDP):能通过 UDP 读写网络中的数据。
  • SocketChannel(TCP Client):能通过 TCP 读写网络中的数据。
  • ServerSocketChannel(TCP Server):可以监听新进来的 TCP 连接,像 Web 服务器那样。对每一个新进来的连接都会创建一个 SocketChannel

buffer 则用来缓冲读写数据,常见的 buffer 有

  • ByteBuffer(用的最多
    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer

Selector

selector 的作用就是配合一个线程来管理多个 channel,获取这些 channel 上发生的事件,这些 channel 工作在非阻塞模式下,不会让线程吊死在一个 channel 上。适合连接数特别多,但流量低的场景(low traffic)

调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件,这些事件发生,select 方法就会返回这些事件交给 thread 来处理

2.ByteBuffer

2.1ByteBuffer的使用

  1. 向 buffer 写入数据,例如调用 channel.read(buffer)
  2. 调用 flip() 切换至读模式
  3. 从 buffer 读取数据,例如调用 buffer.get()
  4. 调用 compact() 或 clear() 切换至写模式,compact()会自动压缩未读的,clear()则会直接清空
  5. 一次可能读不完,重复 1~4 步骤读,
@Slf4j
public class TestByteBuffer {
    public static void main(String[] args) {
        // FileChannel 获得方式
        // 1. 输入输出流, 2. RandomAccessFile
        try (FileChannel channel = new RandomAccessFile("D:\\data.txt", "rw").getChannel()) {
            // 准备缓冲区,指定容量后不可更改
            ByteBuffer buffer = ByteBuffer.allocate(10);
            while(true) {
                // 从 channel 读取数据,向 buffer 写入
                int len = channel.read(buffer);
                log.debug("读取到的字节数 {}", len);
                if(len == -1) { // 没有内容了
                    break;
                }
                // 打印 buffer 的内容
                buffer.flip(); // 切换至读模式
                while(buffer.hasRemaining()) { // 是否还有剩余未读数据
                    byte b = buffer.get();//get()会改变读指针,但get(i)不会,直接根据索引查找位置
                    log.debug("实际字节 {}", (char) b);
                }
                buffer.clear(); // 切换为写模式
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.2ByteBuffer 结构

ByteBuffer的结构可以看成一个连续的数组,有以下重要属性

  • capacity:容量
  • position:起始位置
  • limit:写入/读取限制位置

一开始

写模式下,position 是写入位置,limit 等于容量,下图表示写入了 4 个字节后的状态  

flip 动作发生后,position 切换为读取位置,limit 切换为读取限制  

读取 4 个字节后,状态

clear 动作发生后,状态

compact 方法,是把未读完的部分向前压缩,然后切换至写模式

2.3ByteBuffer的常用方法

分配空间  

分配容量后就不可修改

ByteBuffer byteBuffer1 = ByteBuffer.allocate(容量);//class java.nio.HeapByteBuffer
ByteBuffer byteBuffer2 = ByteBuffer.allocateDirect(容量);//class java.nio.DirectByteBuffer

两种方法返回的实现类不同:

  • HeapByteBuffer:分配在 java 堆内存,读写效率较低,受到 GC(垃圾回收) 的影响
  • DirectByteBuffer:通过调用本地操作系统的内存管理机制来分配堆外内存,读写效率高(不需要通过额外的复制操作将数据从堆内存复制到物理内存),不会受 GC 影响,但分配的效率低,并且如果释放不完全会造成内存泄漏

向 buffer 写入数据

有两种办法

  • 调用 channel 的 read 方法
  • 调用 buffer 自己的 put 方法

从 buffer 读取数据

同样有两种办法

  • 调用 channel 的 write 方法
  • 调用 buffer 自己的 get 方法

get 方法会让 position 读指针向后走,如果想重复读取数据

  • 可以调用 rewind 方法将 position 重新置为 0
  • 或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针

字符串与 ByteBuffer 互转

两种方法:

ByteBuffer buffer1 = StandardCharsets.UTF_8.encode("你好");
ByteBuffer buffer2 = Charset.forName("utf-8").encode("你好");

debug(buffer1);
debug(buffer2);

CharBuffer buffer3 = StandardCharsets.UTF_8.decode(buffer1);
System.out.println(buffer3.getClass());
System.out.println(buffer3.toString());

Buffer 是非线程安全

分散读取、集中写入

2.4调试工具类

netty依赖

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.51.Final</version>
        </dependency>
import io.netty.util.internal.StringUtil;

import java.nio.ByteBuffer;

import static io.netty.util.internal.MathUtil.isOutOfBounds;
import static io.netty.util.internal.StringUtil.NEWLINE;


public class ByteBufferUtil {
    private static final char[] BYTE2CHAR = new char[256];
    private static final char[] HEXDUMP_TABLE = new char[256 * 4];
    private static final String[] HEXPADDING = new String[16];
    private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4];
    private static final String[] BYTE2HEX = new String[256];
    private static final String[] BYTEPADDING = new String[16];

    static {
        final char[] DIGITS = "0123456789abcdef".toCharArray();
        for (int i = 0; i < 256; i++) {
            HEXDUMP_TABLE[i << 1] = DIGITS[i >>> 4 & 0x0F];
            HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F];
        }

        int i;

        // Generate the lookup table for hex dump paddings
        for (i = 0; i < HEXPADDING.length; i++) {
            int padding = HEXPADDING.length - i;
            StringBuilder buf = new StringBuilder(padding * 3);
            for (int j = 0; j < padding; j++) {
                buf.append("   ");
            }
            HEXPADDING[i] = buf.toString();
        }

        // Generate the lookup table for the start-offset header in each row (up to 64KiB).
        for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i++) {
            StringBuilder buf = new StringBuilder(12);
            buf.append(NEWLINE);
            buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L));
            buf.setCharAt(buf.length() - 9, '|');
            buf.append('|');
            HEXDUMP_ROWPREFIXES[i] = buf.toString();
        }

        // Generate the lookup table for byte-to-hex-dump conversion
        for (i = 0; i < BYTE2HEX.length; i++) {
            BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i);
        }

        // Generate the lookup table for byte dump paddings
        for (i = 0; i < BYTEPADDING.length; i++) {
            int padding = BYTEPADDING.length - i;
            StringBuilder buf = new StringBuilder(padding);
            for (int j = 0; j < padding; j++) {
                buf.append(' ');
            }
            BYTEPADDING[i] = buf.toString();
        }

        // Generate the lookup table for byte-to-char conversion
        for (i = 0; i < BYTE2CHAR.length; i++) {
            if (i <= 0x1f || i >= 0x7f) {
                BYTE2CHAR[i] = '.';
            } else {
                BYTE2CHAR[i] = (char) i;
            }
        }
    }

    /**
     * 打印所有内容
     * @param buffer
     */
    public static void debugAll(ByteBuffer buffer) {
        int oldlimit = buffer.limit();
        buffer.limit(buffer.capacity());
        StringBuilder origin = new StringBuilder(256);
        appendPrettyHexDump(origin, buffer, 0, buffer.capacity());
        System.out.println("+--------+-------------------- all ------------------------+----------------+");
        System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), oldlimit);
        System.out.println(origin);
        buffer.limit(oldlimit);
    }

    /**
     * 打印可读取内容
     * @param buffer
     */
    public static void debugRead(ByteBuffer buffer) {
        StringBuilder builder = new StringBuilder(256);
        appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position());
        System.out.println("+--------+-------------------- read -----------------------+----------------+");
        System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), buffer.limit());
        System.out.println(builder);
    }

    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put(new byte[]{97, 98, 99, 100});
        debugAll(buffer);
    }

    private static void appendPrettyHexDump(StringBuilder dump, ByteBuffer buf, int offset, int length) {
        if (isOutOfBounds(offset, length, buf.capacity())) {
            throw new IndexOutOfBoundsException(
                    "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length
                            + ") <= " + "buf.capacity(" + buf.capacity() + ')');
        }
        if (length == 0) {
            return;
        }
        dump.append(
                "         +-------------------------------------------------+" +
                        NEWLINE + "         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |" +
                        NEWLINE + "+--------+-------------------------------------------------+----------------+");

        final int startIndex = offset;
        final int fullRows = length >>> 4;
        final int remainder = length & 0xF;

        // Dump the rows which have 16 bytes.
        for (int row = 0; row < fullRows; row++) {
            int rowStartIndex = (row << 4) + startIndex;

            // Per-row prefix.
            appendHexDumpRowPrefix(dump, row, rowStartIndex);

            // Hex dump
            int rowEndIndex = rowStartIndex + 16;
            for (int j = rowStartIndex; j < rowEndIndex; j++) {
                dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
            }
            dump.append(" |");

            // ASCII dump
            for (int j = rowStartIndex; j < rowEndIndex; j++) {
                dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
            }
            dump.append('|');
        }

        // Dump the last row which has less than 16 bytes.
        if (remainder != 0) {
            int rowStartIndex = (fullRows << 4) + startIndex;
            appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);

            // Hex dump
            int rowEndIndex = rowStartIndex + remainder;
            for (int j = rowStartIndex; j < rowEndIndex; j++) {
                dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
            }
            dump.append(HEXPADDING[remainder]);
            dump.append(" |");

            // Ascii dump
            for (int j = rowStartIndex; j < rowEndIndex; j++) {
                dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
            }
            dump.append(BYTEPADDING[remainder]);
            dump.append('|');
        }

        dump.append(NEWLINE +
                "+--------+-------------------------------------------------+----------------+");
    }

    private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {
        if (row < HEXDUMP_ROWPREFIXES.length) {
            dump.append(HEXDUMP_ROWPREFIXES[row]);
        } else {
            dump.append(NEWLINE);
            dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L));
            dump.setCharAt(dump.length() - 9, '|');
            dump.append('|');
        }
    }

    public static short getUnsignedByte(ByteBuffer buffer, int index) {
        return (short) (buffer.get(index) & 0xFF);
    }

2.5黏包、半包问题

黏包(Packet Concatenation)和半包(Incomplete Packet)问题是在网络通信中常见的两个问题。它们涉及到数据的传输和接收不完整或混淆的情况。

黏包问题(Packet Pasting):黏包问题指的是在网络通信中,由于数据传输速度快于数据处理速度,多个数据包可能会在接收端被一次性接收到,导致它们被"黏"在一起,无法准确分辨每个数据包的界限。这可能会导致数据解析错误或混乱。

例如,发送端发送了两个数据包,但接收端可能会一次性接收到这两个数据包,从而形成一个"黏包"。解决这个问题的方法通常涉及在数据包中添加长度信息或特殊分隔符,以便接收端能够正确地切分数据包。

半包问题(Partial Packet):半包问题是指在数据传输中,数据包没有完整地传输完成就被接收端接收到,造成接收到的数据包不完整,即"半包"。这可能会导致数据不完整或无法正确解析。

例如,发送端发送一个较大的数据包,但在传输过程中被切分成多个片段,接收端可能只接收到其中的一部分,导致数据不完整。解决这个问题的方法通常是在数据包中添加长度信息,确保接收端能够正确地等待和组装完整的数据包。

3.文件编程FileChannel

FileChannel 只能工作在阻塞模式下,其他与网络有关的Channel则有阻塞模式与非阻塞模式两种

3.1常用方法

获取

不能直接打开 FileChannel,必须通过 FileInputStream、FileOutputStream 或者RandomAccessFile 来获取 FileChannel,它们都有 getChannel 方法

  • 通过 FileInputStream 获取的 channel 只能读
  • 通过 FileOutputStream 获取的 channel 只能写
  • 通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式(rw)决定

读取

会从 channel 读取数据填充 ByteBuffer,返回值表示读到了多少字节,-1 表示到达了文件的末尾

int readBytes = channel.read(buffer);

写入

ByteBuffer buffer = ...;
buffer.put(...); // 存入数据
buffer.flip();   // 切换读模式

while(buffer.hasRemaining()) {
    channel.write(buffer);
}

在 while 中调用 channel.write 是因为 write 方法并不能保证一次将 buffer 中的内容全部写入 channel

关闭

channel 必须关闭,不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法会间接地调用 channel 的 close 方法

3.2两个Channel之间传输数据

超过 2g 大小的文件传输:

transferTo(起始位置,传输数,传输目标地)

public class TestFileChannelTransferTo {
    public static void main(String[] args) {
        try (
                FileChannel from = new FileInputStream("data.txt").getChannel();
                FileChannel to = new FileOutputStream("to.txt").getChannel();
        ) {
            // 效率高,底层会利用操作系统的零拷贝进行优化
            long size = from.size();
            // left 变量代表还剩余多少字节
            for (long left = size; left > 0; ) {
                System.out.println("position:" + (size - left) + " left:" + left);
                left -= from.transferTo((size - left), left, to);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/80587.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AMD fTPM RNG的BUG使得Linus Torvalds不满

导读因为在 Ryzen 系统上对内核造成了困扰&#xff0c;Linus Torvalds 最近在邮件列表中表达了对 AMD fTPM 硬件随机数生成器的不满&#xff0c;并提出了禁用该功能的建议。 因为在 Ryzen 系统上对内核造成了困扰&#xff0c;Linus Torvalds 最近在邮件列表中表达了对 AMD fTPM…

原生js获取今天、昨天、近7天的时间(年月日时分秒)

有的时候我们需要将今天,昨天,近7天的时间(年月日时分秒)作为参数传递给后端,如下图: 那怎么生成这些时间呢?如下代码里,在methods里的toDay方法、yesterDay方法、weekDay方法分别用于生成今天、昨天和近7天的时间: <template><div class="box"&…

【校招VIP】测试方案之测试用例分析

考点介绍 测试用例是测试岗面试和工作后的核心&#xff0c;在面试里对测试用例的分析是高频考查点。但是很多同学因为没有真实的商业产品需求&#xff0c;只能简单的看别人的用例学习&#xff0c;导致面试时被一个陌生问题卡住。 比如最简单的用户名密码输入&#xff0c;在商业…

⛳ Docker - Centos 安装配置

目录 ⛳ Docker - Centos 安装配置&#x1f3ed; Docker 安装&#xff1a;&#x1f4e2; 一、安装依赖包&#x1f4ac; 二、添加 Docker 下载源地址&#x1f43e; 三、更新yum缓存&#x1f463; 四、安装Docker&#x1f4bb; 五、启动Docker&#x1f381; 六、查看Docker状态和…

vue3 + antv/x6 实现拖拽侧边栏节点到画布

前篇&#xff1a;vue3ts使用antv/x6 自定义节点 前篇&#xff1a;vue3antv x6自定义节点样式 1、创建侧边栏 用antd的menu来做侧边栏 npm i --save ant-design-vue4.x//入口文件main.js内 import Antd from ant-design-vue; import App from ./App; import ant-design-vue/…

OpenFOAM的fvOptions

采用OpenFoam中的fvOptions /*--------------------------------*- C -*----------------------------------*\ |\\ / F ield | OpenFOAM: The Open Source CFD Toolbox\\ / O peration | Website: https://openfoam.org\\ / A n…

安卓框架中的常见问题汇总

目录 1.安卓操作系统的组件结构图如下 2.问题汇总 1.安卓操作系统的组件结构图如下 2.问题汇总 问题1&#xff1a;安卓框架中的库和应用程序框架之间什么关系&#xff1f; 在安卓系统中&#xff0c;应用程序框架层&#xff08;Application Framework&#xff09;是核心应用程…

ADC静态特性测试

测试环境搭建&#xff1a; 码密度分析法的局限性 更新&#xff1a; MATLAB R2020a之后的版本&#xff0c;更新了函数 “inldnl()”&#xff0c;可以自动计算INL和DNL。具体用法看MATLAB说明文档即可。

【FAQ】调用视频汇聚平台EasyCVR的iframe地址,视频无法播放的原因排查

有用户反馈&#xff0c;在调用iframe地址后嵌入用户自己的前端页面&#xff0c;视频无法播放并且要求登录。 安防监控视频汇聚平台EasyCVR基于云边端一体化架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;可提供视频监控直播、云端录像、视频云存储、视频集中…

【面试题】2、Docker和Spring相关

1、Docker是什么&#xff1f; &#xff08;1&#xff09;Docker是一个快速交互、运行应用的技术&#xff0c;可以将程序及其依赖、运行环境一起打包为一个镜像&#xff0c;该镜像可以迁移到任意的Linux操作系统 &#xff08;2&#xff09;运行时利用沙箱机制形成隔离容器&…

【Spring Cloud 八】Spring Cloud Gateway网关

gateway网关 系列博客背景一、什么是Spring Cloud Gateway二、为什么要使用Spring Cloud Gateway三、 Spring Cloud Gateway 三大核心概念4.1 Route&#xff08;路由&#xff09;4.2 Predicate&#xff08;断言&#xff09;4.3 Filter&#xff08;过滤&#xff09; 五、Spring …

无脑入门pytorch系列(三)—— nn.Linear

本系列教程适用于没有任何pytorch的同学&#xff08;简单的python语法还是要的&#xff09;&#xff0c;从代码的表层出发挖掘代码的深层含义&#xff0c;理解具体的意思和内涵。pytorch的很多函数看着非常简单&#xff0c;但是其中包含了很多内容&#xff0c;不了解其中的意思…

8.1.5:Extreme Optimization Numerical Libraries for .NET

构建金融、工程和科学应用程序。 Extreme Optimization Numerical Libraries for .NET 是通用数学和统计类的集合。它为基于 Microsoft .NET 平台构建的技术和统计计算提供了一个完整的平台。它将数学库、向量和矩阵库以及统计库结合在一个方便的包中。 一般特征 即使对数学不太…

wxPython两个页面跳转简单方案

import wxclass Frame1(wx.Frame):def __init__(self, parent):super().__init__(parent)panel wx.Panel(self)button wx.Button(panel, label"跳转到Frame2")button.Bind(wx.EVT_BUTTON, self.on_button_click)sizer wx.BoxSizer(wx.VERTICAL)sizer.Add(button, …

ArduPilot开源飞控之飞行模式

ArduPilot开源飞控之飞行模式 1. 源由2. 飞行模式-已实现3. 飞行模式-设计3.1 模式初始化(init)3.2 模式退出(exit)3.3 模式任务(run)3.4 模式切换场景3.4.1 上电初始化3.4.2 EKF FAILSAFE触发3.4.3 do_failsafe_action FAILSAFE触发3.4.4 AP_Avoidance_Copter触发3.4.5 Crash触…

水库大坝安全监测MCU,提升大坝管理效率的利器!

水库大坝作为防洪度汛的重要设施&#xff0c;承担着防洪抗旱&#xff0c;节流发电的重要作用。大坝的安全直接关系到水库的安全和人民群众的生命财产安全。但因为水库大坝的隐患不易被察觉&#xff0c;发现时往往为时已晚。因此&#xff0c;必须加强对大坝的安全管理。其安全监…

linux 系统中vi 编辑器和库的制作和使用

目录 1 vim 1.1 vim简单介绍 1.2 vim的三种模式 1.3 vim基本操作 1.3.1命令模式下的操作 1.3.2 切换到文本输入模式 1.3.3 末行模式下的操作 2 gcc编译器 2.1 gcc的工作流程 2.2 gcc常用参数 3 静态库和共享&#xff08;动态&#xff09;库 3.1库的介绍 3.2静态…

blender 发射体粒子

发射体粒子的基础设置 选择需要添加粒子的物体&#xff0c;点击右侧粒子属性&#xff0c;在属性面板中&#xff0c;点击加号&#xff0c;物体表面会出现很多小点点&#xff0c;点击空格键&#xff0c;粒子会自动运动&#xff0c;像下雨一样&#xff1b; bender 粒子系统分为两…

AI 绘画Stable Diffusion 研究(八)sd采样方法详解

大家好&#xff0c;我是风雨无阻。 本文适合人群&#xff1a; 希望了解stable Diffusion WebUI中提供的Sampler究竟有什么不同&#xff0c;想知道如何选用合适采样器以进一步提高出图质量的朋友。 想要进一步了解AI绘图基本原理的朋友。 对stable diffusion AI绘图感兴趣的朋…

2023国赛数学建模思路 - 案例:ID3-决策树分类算法

文章目录 0 赛题思路1 算法介绍2 FP树表示法3 构建FP树4 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模…