常见面试题-Netty专栏(一)

news2024/11/16 13:26:39

typora-copy-images-to: imgs

Netty 是什么呢?Netty 用于做什么呢?

答:

Netty 是一个 NIO 客户服务端框架,可以快速开发网络应用程序,如协议服务端和客户端,极大简化了网络编程,如 TCP 和 UDP 套接字服务(来自官网)

热门开源项目如 Dubbo、RocketMQ 底层都是用了 Netty

Netty怎么实现高性能设计?

答:

Netty 高性能的三个方面:

  1. 传输:用什么样的通道将数据发送给对方,BIO、NIO 或者 AIO,IO 模型在很大程度上决定了框架的性能。IO模型的选择
  2. 协议:采用什么样的通信协议,HTTP 或者内部私有协议。协议的选择不同,性能模型也不同。相比于公有协议,内部私有协议的性能通常可以被设计的更优。协议的选择
  3. 线程:数据报如何读取?读取之后的编解码在哪个线程进行,编解码后的消息如何派发,Reactor 线程模型的不同,对性能的影响也非常大。线程模型的选择

介绍一下 AIO、BIO 和 NIO?

答:

  • AIO:

从 Java.1.7 开始,Java 提供了 AIO(异步IO),Java 的 AIO 也被称为 “NIO.2”

Java AIO 采用订阅-通知模式,应用程序向操作系统注册 IO 监听,之后继续做自己的事情,当操作系统发生 IO 事件并且已经准备好数据时,主动通知应用程序,应用程序再进行相关处理

(Linux 平台没有这种异步 IO 技术,而是使用 epoll 对异步 IO 进行模拟)

  • BIO:

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

BIO简单工作流程:

  1. 服务器端启动一个 ServerSocket
  2. 客户端启动 Socket 对服务器进行通信,默认情况下服务器端需要对每个客户端建立一个线程与之通讯
  3. 客户端发出请求后, 先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝
  4. 如果有响应,客户端线程会等待请求结束后,再继续执行

BIO存在问题:

  1. 当并发量较大时,需要创建大量线程来处理连接,比较占用系统资源
  2. 连接建立之后,如果当前线程暂时没有数据可读,则线程会阻塞在 Read 操作上,造成线程资源浪费
  • NIO:

从 Java1.4 开始,Java 提供了 NIO,NIO 即 “Non-blocking IO”(同步非阻塞IO)

NIO 的几个核心概念:

  1. Channel、Buffer:BIO是基于字节流或者字符流的进行操作,而NIO 是基于缓冲区通道进行操作的,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中

  2. Selector:选择器用于监听多个通道的事件(如,连接打开,数据到达),因此,单个线程可以监听多个数据通道,极大提升了单机的并发能力

    当 Channel 上的 IO 事件未到达时,线程会在 select 方法被挂起,让出 CPU 资源,直到监听到 Channel 有 IO 事件发生,才会进行相应的处理

  • NIO和BIO有什么区别?
  1. NIO是以的方式处理数据,BIO是以字节流或者字符流的形式去处理数据。
  2. NIO是通过缓存区和通道的方式处理数据,BIO是通过InputStream和OutputStream流的方式处理数据。
  3. NIO的通道是双向的,BIO流的方向只能是单向的。
  4. NIO采用的多路复用的同步非阻塞IO模型,BIO采用的是普通的同步阻塞IO模型。
  5. NIO的效率比BIO要高,NIO适用于网络IO,BIO适用于文件IO。

NIO如何实现了同步非阻塞?

通过 Selector 和 Channel 来进行实现,一个线程使用一个 Selector 监听多个 Channel 上的 IO 事件,通过配置监听的通道Channel为非阻塞,那么当Channel上的IO事件还未到达时,线程会在select方法被挂起,让出CPU资源。直到监听到Channel有IO事件发生时,才会进行相应的响应和处理。

介绍一下 Netty 使用的线程模型?

答:

Netty 主要基于主从 Reactor 多线程模型,其中主从 Reactor 多线程模型将 Reactor 分为两部分:

  • mainReactor:监听 Server Socket,用来处理网络 IO 连接建立操作,将建立的 SocketChannel 指定注册给 subReactor
  • subReactor:和建立起来的 socket 做数据交互和业务处理操作

因为客户端的连接数量相对来说比较少,而数据的读和写会比较多一点,使用 mainReactor 只接受客户端连接,由其他线程 subReactor 负责读和写,将业务处理剥离出,让线程池来处理,降低了 Reactor 的性能开销

扩展:单 Reactor 单线程模型、单 Reactor 多线程模型

  • 单 Reactor 单线程模型

    通过 1 个线程负责客户端连接、网络数据的读写、业务处理

    缓存 Redis 就是单 Reactor 单线程模型

  • 单 Reactor 多线程模型

    通过 1 个线程负责客户端的连接、网络数据的读写,将业务处理剥离出去,通过线程池来进行处理

三种 Reactor 模型的优缺点:

  • 单 Reactor 单线程模型是单线程进行业务处理,当负载过重时,处理速度将会变慢,影响系统性能,因此引出单 Reactor 多线程模型
  • 单 Reactor 多线程模型时多个线程处理业务,业务处理速度上来了,但是单 Reactor 承担了所有时间的监听和响应,可能存在性能问题。当有数百万客户端进行连接或者服务端需要对客户端握手进行安全认证,认证本身非常消耗性能,因此出现了主从 Reactor 多线程模型
  • 主从 Reactor 多线程模型中 1 个主 Reactor 只用来处理网络 IO 的连接建立操作,而对于接入认证、IP 黑白名单过滤、握手等操作由从 Reactor 进行处理,这样进一步提升性能,在主从 Reactor 多线程模型中,从 Reactor 有多个,可以与 CPU 个数相同

TCP 粘包、拆包是什么?如何解决?

答:

TCP本身的机制决定了一定会有粘包、拆包,因为 TCP 传输协议时基于数据流传输的,而流化的数据没有界限,因此 TCP 作为传输层协议并不了解上层业务数据的具体含义,会根据 TCP 缓冲区的实际情况进行数据包的划分,所以业务上认为的一个完整的包,可能被 TCP 拆成多个包或者把多个小的包封装成一个大的包进行发送。

产生原因:

  • 粘包:客户端发送的包的大小比socket的缓存小或者接收方读取socket缓存不及时,因此多个包一起发送了
  • 拆包:客户端发送的包的大小比socket的缓存大或者发送的数据大于协议的MTU(最大传输单元)必须拆包,那么这个包就被拆分成了多个包进行发送

解决方法:

有三种方式:

  • 通过指定分隔符来进行分割
  • 通过指定固定长度来进行分割
  • 上边两种方式灵活性不好,因此常用的是通过指定接收数据的长度来解决,也就是LengthFieldBasedFrameDecoder()这个类

Netty 中常用组件?

答:

  • Channel:Netty 网络操作抽象类,包括了基本的 IO 操作,如 bind、connect、read、write 等等
  • EventLoop:主要是配合 Channel 处理 IO 操作,用来处理连接的生命周期中所发生的事件
  • ChannelFuture:Netty 中的所有 IO 操作都是异步的,我们通过 ChannelFuture 的 addListener() 注册一个 ChannelFutureListener 监听事件,当操作执行完毕后,监听就会返回结果
  • ChannelHandler:作为处理器,用于处理入站和出战的数据
  • ChannelHandlerContext:用于包裹 ChannelHandler,维护了 pipeline 这个双向链表中的 pre 和 next 指针,这样可以方便的找到与其相邻的 ChannelHandler,并且过滤出一些符合执行条件的 ChannelHandler,Netty 的异步事件在 pipeline 中传播就是依靠 ChannelHandlerContext
  • ChannelPipeline:每一个 Channel 都会分配一个 ChannelPipeline,pipeline 是一个双向链表的结构,Netty 中产生的 IO 异步事件都会在这个 pipeline 中传播

Netty 如何发送消息?

答:

有两种发送消息的方式:

  • 直接写入 Channel,消息从 ChannelPipeline 的尾节点开始向前传播至头节点,代码channelHandlerContext.write()
  • 使 write 事件从当前 ChannelHandler 开始沿着 pipeline 向前传播,代码channelHandlerContext.channel().write()

这里解释一下,上边发送消息为什么是向前传播:

在 Netty 中,IO 异步事件基本上分为两类:inbound(入站) 事件、outbound(出站) 事件,那么入站事件是沿着 pipeline 的头结点一直向后传播,因此出站事件就是沿着 pipeline 的尾结点一直向前传播,而上边发送消息也就是出站事件,因此是沿着 pipeline 从后向前进行传播

直接内存比堆内存快在了哪里?

答:

首先直接内存不是 Java 虚拟机中的内存,是直接向系统内存申请的空间,来源于 NIO,通过 Java 堆中的 DirectByteBuffer 来进行操作。

直接内存相比于堆内存,避免了数据的二次拷贝。

  • 我们先来分析不使用直接内存的情况,我们发送数据需要将数据先写入 Socket 的缓冲区内,那么如果数据存储在 JVM 的堆内存中的话,会先将堆内存中的数据复制一份到直接内存中,再将直接内存中的数据写入到 Socket 缓冲区中,之后进行数据的发送

    • 为什么不能直接将 JVM 堆内存中的数据写入 Socket 缓冲区中呢?

      在 JVM 堆内存中有 GC 机制,GC 后可能会导致堆内存中数据位置发生变化,那么如果直接将 JVM 堆内存中的数据写入 Socket 缓冲区中,如果写入过程中发生 GC,导致我们需要写入的数据位置发生变化,就会将错误的数据写入 Socket 缓冲区

  • 那么如果使用直接内存的时候,我们将数据直接存放在直接内存中,在堆内存中只存放了对直接内存中数据的引用,这样在发送数据时,直接将数据从直接内存取出,放入 Socket 缓冲区中即可,减少了一次堆内存到直接内存的拷贝

请添加图片描述

什么是 Netty 的零拷贝?什么是 TCP 缓冲区?

答:

Netty 的零拷贝主要包含三个方面:

  • Netty 的接收和发送使用堆外(直接内存)进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。

  • Netty 提供 CompositeByteBuf 组合缓冲区类,可以将多个 ByteBuf合并为一个逻辑上的ByteBufer,避免了各个ByteBufer之间的拷贝,将几个小buffer合并成一个大buffer的繁琐操作。

  • Netty 的文件传输使用了 FileChanneltransferTo 方法,该方法底层使用了 sendfile 函数实现了 cpu 零拷贝。

    sendfile 函数通过网络发送数据的流程为:

    磁盘 ----DMA拷贝---->文件读取缓冲区 ----CPU拷贝----> 套接字发送缓冲区(SO_SNDBUF) ----DMA拷贝----> 网络设备缓冲区(网卡)

    图片流程如下:(3次拷贝、2次上下文切换,这里上下文切换是在用户空间发起write操作,此时用户态切换为内核态,write调用完毕后又会从内核态切换回用户态)

    请添加图片描述

什么是TCP 缓冲区?

每个 TCP 的 Socket 的内核中都有一个发送缓冲区(SO_SNDBUF)和一个接收缓冲区(SO_RECVBUF)(在通过 TCP 需要进行网络数据传输时,数据都是会写入 Socket 的缓冲区中的,之后再通过网络协议发送出去)

了解 Netty 中的 ByteBuf 类吗?

答:

在 Java NIO 编程中,Java 提供了 ByteBuffer 作为字节缓冲区类型(缓冲区可以理解为一段内存区域),来表示一个连续的字节序列。

Netty 中并没有使用 Java 的 ByteBuffer,而是使用了新的缓冲类型 ByteBuf,特性如下:

  • 允许自定义缓冲类型

  • 复合缓冲类型中内置的透明的零拷贝实现

  • 开箱即用的动态缓冲类型,具有像 StringBuffer 一样的动态缓冲能力

  • 不再需要调用 flip() 方法

    Java 的 ByteBuffer 类中,需要使用 flip() 来进行读写两种模式的切换

  • 正常情况下具有比 ByteBuffer 更快的响应速度

Java 中的 ByteBuffer:

主要需要注意有 3 个属性:position、limit、capacity

  • capacity:当前数组的容量大小
  • position:写入模式的可写入数据的下标,读取模式的可读取数据下标
  • limit:写入模式的可写入数组大小,读取模式的最多可以读取数据的下标

假如说数组容量是 10,那么三个值初始值为:

position = 0
limit = 10
capacity = 10

假如写入 4 个字节的数据,此时三个值如下:

position = 4
limit = 10
capacity = 10

如果切换到读取数据模式(使用 flip()),会改变上边的三个值,会从 position 的位置开始读取数据到 limit 的位置

position = 0
limit = 4
capacity = 10

Netty 中的 ByteBuf:

ByteBuf 主要使用两个指针来完成缓冲区的读写操作,分别是: readIndexwriteIndex

  • 当写入数据时,writeIndex 会增加
  • 当读取数据时,readIndex 会增加,但不会超过 writeIndex

ByteBuf 的使用:

public static void main(String[] args) {
    ByteBuf buffer = Unpooled.buffer(10);
    System.out.println("----------初始化ByteBuf----------");
    printByteBuffer(buffer);

    System.out.println("----------ByteBuf写入数据----------");
    String str = "hello world!";
    buffer.writeBytes(str.getBytes());
    printByteBuffer(buffer);

    System.out.println("----------ByteBuf读取数据----------");
    while (buffer.isReadable()) {
        System.out.print((char)buffer.readByte());
    }
    System.out.println();
    printByteBuffer(buffer);


    System.out.println("----------ByteBuf释放无用空间----------");
    buffer.discardReadBytes();
    printByteBuffer(buffer);

    System.out.println("----------ByteBuf清空----------");
    buffer.clear();
    printByteBuffer(buffer);
}
private static void printByteBuffer(ByteBuf buffer) {
    System.out.println("readerIndex:" + buffer.readerIndex());
    System.out.println("writerIndex:" + buffer.writerIndex());
    System.out.println("capacity:" + buffer.capacity());
}
/**输出**/
----------初始化ByteBuf----------
readerIndex:0
writerIndex:0
capacity:10
----------ByteBuf写入数据----------
readerIndex:0
writerIndex:12
capacity:64
----------ByteBuf读取数据----------
hello world!
readerIndex:12
writerIndex:12
capacity:64
----------ByteBuf释放无用空间----------
readerIndex:0
writerIndex:0
capacity:64
----------ByteBuf清空----------
readerIndex:0
writerIndex:0
capacity:64

ByteBuf 的 3 种使用模式:

ByteBuf 共有 3 种使用模式:

  • 堆缓冲区模式(Heap Buffer)

    堆缓冲区模式又称为 “支撑数据”,其数据存放在 JVM 的堆空间

    优点:

    • 数据在 JVM 堆中存储,可以快速创建和释放,并且提供了数组直接快速访问的方法

    缺点:

    • 每次数据与 IO 进行传输时,都需要将数据复制到直接缓冲区(这里为什么要将数据复制到直接缓冲区的原因在上边的 直接内存比堆内存快在了哪里? 问题中已经讲过)

    创建代码:

    ByteBuf buffer = Unpooled.buffer(10);
    
  • 直接缓冲区模式(Direct Buffer)

    直接缓冲区模式属于堆外分配的直接内存,不占用堆的容量

    优点:

    • 使用 socket 传输数据时性能很好,避免了数据从 JVM 堆内存复制到直接缓冲区

    缺点:

    • 相比于堆缓冲区,直接缓冲区分配内存空间和释放更为昂贵

    创建代码:

    ByteBuf buffer = Unpooled.directBuffer(10);
    
  • 复合缓冲区模式(Composite Buffer)

    本质上类似于提供一个或多个 ByteBuf 的组合视图

    优点:

    • 提供一种方式让使用者自由组合多个 ByteBuf,避免了复制和分配新的缓冲区

    缺点:

    • 不支持访问其支撑数据,如果要访问,需要先将内容复制到堆内存,再进行访问

    创建代码:

    public static void main(String[] args) {
    //        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Test.class);
        // 创建一个堆缓冲区
        ByteBuf heapBuf = Unpooled.buffer(2);
        String str1 = "hi";
        heapBuf.writeBytes(str1.getBytes());
        // 创建一个直接缓冲区
        ByteBuf directBuf = Unpooled.directBuffer(5);
        String str2 = "nihao";
        directBuf.writeBytes(str2.getBytes());
        // 创建一个复合缓冲区
        CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(10);
        compositeByteBuf.addComponents(heapBuf, directBuf);
        // 检查是否支持支撑数组,发现并不支持
        if (!compositeByteBuf.hasArray()) {
            for (ByteBuf buf : compositeByteBuf) {
                // 第一个字节偏移量
                int offset = buf.readerIndex();
                // 总共数据长度
                int length = buf.readableBytes();
                byte[] bytes = new byte[length];
                // 不支持访问支撑数组,需要将内容复制到堆内存中,即 bytes 数组中,才可以进行访问
                buf.getBytes(offset, bytes);
                printByteBuffer(bytes, offset, length);
            }
        }
    }
    
    private static void printByteBuffer(byte[] array, int offset, int length) {
        System.out.println("array:" + array);
        System.out.println("array->String:" + new String(array));
        System.out.println("offset:" + offset);
        System.out.println("len:" + length);
    }
    /**输出**/
    array:[B@4f8e5cde
    array->String:hi
    offset:0
    len:2
    array:[B@504bae78
    array->String:nihao
    offset:0
    len:5
    

Netty 中 ByteBuf 如何分配?有池化的操作吗?

答:

ByteBuf 的分配接口定义在了 ByteBufAllocator 中,他的直接抽象类是 AbstracByteBufAllocator,而 AbstractByteBufAllocator 有两种实现:PooledByteBufAllocatorUnpooledByteBufAllocator
请添加图片描述

  • PooledByteBufAllocator 提供了池化的操作,将 ByteBuf 实例放入池中,提升了性能,将内存碎片化减到了最小UnpooledByteBufAllocator。(这个实现采用了一种内存分配的高效策略,成为 jemalloc,已经被好几种现代操作系统所采用)
  • UnpooledByteBufAllocator 在每次创建缓冲区时,都会返回一个新的 ByteBuf 实例,这些实例由 JVM 负责 gc 回收

NioEventLoopGroup 默认启动了多少线程?

答:

NioEventLoopGroup 是一个多线程的事件循环器,默认启动了电脑可用线程数的两倍,在调用 NioEventLoopGroup 的构造方法之后,如果不传入线程数,最后启动的默认线程数的计算公式为:
请添加图片描述

Netty 如何解决 Selector 空轮询 Bug 的策略

答:

空轮询 Bug 也就是网络上发生了唤醒 Selector 的事件,但是 Selector 去取事件取不到,就一直去取,发生了空轮询,导致 CPU 使用率达到 100%

Netty解决机制:

判断如果发生了 N(默认是512次) 次空轮询,就新建一个Selector,把原来Selector事件都迁移过来

为什么没有使用 Netty 5?

答:

Netty版本分别是netty3.x、netty4.x、netty5.x
Netty5出现重大bug,已经被官网废弃,目前推荐使用Netty4.x稳定版本

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手搭手Ajax经典基础案例省市联动

环境介绍 技术栈 springbootmybatis-plusmysql 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http:/…

MYSQL(事务+锁+MVCC+SQL执行流程)理解

一)事务的特性: 一致性:主要是在数据层面来说&#xff0c;不能说执行扣减库存的操作的时候用户订单数据却没有生成 原子性:主要是在操作层面来说&#xff0c;要么操作完成&#xff0c;要么操作全部回滚&#xff1b; 隔离性:是自己的事务操作自己的数据&#xff0c;不会受到到其…

有哪些好用的程序员接私活平台?

程序员如何在苦逼的生活中&#xff0c;呼吸一口富贵又自由的空气? 接单的话&#xff0c;如何脱颖而出&#xff1f; 又该用什么平台呢&#xff1f;哪些平台会更靠谱一点呢&#xff1f; 会不会被坑? balabalabalabala......太多问题了&#xff0c;核心还是不了解这里面的详情。…

从理解概念开始,彻底学会linux下的磁盘扩容操作

对于linux磁盘空间不足需要扩容的情况&#xff0c;其他文章一般只介绍要如何操作&#xff0c;使用什么样的命令&#xff0c;但是不去介绍为什么要这么做&#xff0c;搞得好多小白一头雾水。本文从linux的文件系统开始讲起&#xff0c;帮你彻底学会linux系统中的磁盘扩容操作。 …

科学指南针iThenticate自助查重系统重磅上线

科学指南针&#xff0c;一直致力于为科研工作者提供高效、专业的学术支持&#xff0c;近日推出了全新的iThenticate自助查重系统。这一系统的上线&#xff0c;旨在为广大科研工作者提供更加便捷、准确的论文查重服务&#xff0c;进一步规范英文使用&#xff0c;提升科研质量。 …

使用Packstack安装器安装一体化OpenStack云平台

【实训目的】 初步掌握OpenStack快捷安装的方法。掌握OpenStack图形界面的基本操作。 【实训准备】 &#xff08;1&#xff09;准备一台能够安装OpenStack的实验用计算机&#xff0c;建议使用VMware虚拟机。 &#xff08;2&#xff09;该计算机应安装CentOS 7&#xff0c;建…

【网络协议】聊聊网络路由相关算法

如何配置路由 路由器是一台网络设备&#xff0c;多张网卡&#xff0c;当一个入口的网络包到达路由器时&#xff0c;会根据本地的信息库决定如何正确的转发流量&#xff0c;通常称为路由表 路由表主要包含如下 核心思想是根据目的 IP 地址来配置路由 目的网络&#xff1a;要去…

电影评分数据分析案例-Spark SQL

# cording:utf8from pyspark.sql import SparkSession from pyspark.sql.types import IntegerType, StringType, StructType import pyspark.sql.functions as Fif __name__ __main__:# 0.构建执行环境入口对象SparkSessionspark SparkSession.builder.\appName(movie_demo)…

DDOS直接攻击系统资源

DDOS ——直接攻击系统资源 思路&#xff1a; 攻击机利用三次握手机制&#xff0c;产生大量半连接&#xff0c;挤占受害者系统资源&#xff0c;使其无法正常提供服务。 1、先体验下受害者的正常网速。在受害者主机上执行以下命令 (1)开启Apache。 systemctl start apache2 (2…

C++数据结构X篇_20_选择排序

文章目录 1. 选择排序原理2. 选择排序原理核心代码3. 选择排序时间消耗 1. 选择排序原理 选择排序&#xff1a;相对于冒泡排序&#xff0c;减少了交换次数&#xff0c;下图展示了选择排序的原理&#xff0c;具体仍需要结合代码分析。 2. 选择排序原理核心代码 //选择排序 v…

运行报错(三)git bash报错fatal: detected dubious ownership in repository at

报错现象 在运行git 命令时&#xff0c;出现报错 “fatal: detected dubious ownership in repository at” 报错原因 文件夹的所有者和现在的用户不一致 栗子&#xff1a; 文件夹的所有者是root&#xff0c;而当前用户是admin 解决方案 方法一、 将文件夹的所有者替换成ad…

九章云极DataCanvas公司入选Forrester AI/ML权威报告

日前&#xff0c;全球研究机构Forrester最新发布了《The Forrester Wave™: AI/ML Platforms In China, Q4 2023》报告&#xff08;以下简称“报告”&#xff09;。凭借DataCanvas APS机器学习平台这一人工智能核心基础软件的持续研发和广泛应用&#xff0c;九章云极DataCanvas…

数据集-特征降维

1、降维 降维是指在某些限定条件下&#xff0c;降低随机变量(特征)个数&#xff0c;得到一组“不相关”主变量的过程 降低随机变量的个数 相关特征(correlated feature) 相对湿度与降雨量之间的相关等等 正是因为在进行训练的时候&#xff0c;我们都是使用特征进行学习。如果…

基于 Android 的文件同步设计方案

1、背景 随着用户对自身数据保护意识的加强&#xff0c;让用户自己维护自己的数据也成了独立开发产品时的一个卖点。若只针对少量的文件进行同步&#xff0c;则实现起来比较简单。当针对一个多层级目录同步时&#xff0c;情况就复杂多了。鉴于相关的文章甚少&#xff0c;本文我…

MODIS数据产品预处理方法

1 MCTK重投影 第一步&#xff1a;安装ENVI的MCTK扩展工具 解压压缩包&#xff0c;将其中的mctk.sav与modis_products.scsv文件复制到如图所示&#xff0c;相应的ENVI安装路径中去。 第二步&#xff1a;打开ENVI5.3标准版如图所示 在右边的工具栏处打开最下方的Extensions工具…

代码随想录笔记--单调栈篇

1--单调栈 使用单调栈的特征&#xff1a;寻找第一个比当前元素大或者小的元素。 2--每日温度 主要思路&#xff1a; 基于单调栈&#xff0c;单调栈从栈顶开始递增&#xff1b;单调栈存储的是元素对应的索引。 当遇到一个元素大于栈顶元素i时&#xff0c;计算 answer[i]。 #incl…

腾讯待办关停之后还能用吗?可替代的待办提醒APP

如果你之前喜欢用“腾讯待办”这款微信小程序设置待办提醒&#xff0c;那么接下来不得不面对一个事实&#xff1a;腾讯待办将于2023年的12月20日全面停止运营并下架。如果在这款小程序中记录了很多的待办事项&#xff0c;现在应该尽快导出数据&#xff0c;避免数据丢失。 还有…

Rowset Class

Rowset类在PeopleCode中非常常见&#xff0c;以下将Rowset翻译成行集&#xff0c;顾名思义&#xff0c;行的集合 目录 Understanding Rowset Class Shortcut Considerations Data Type of a Rowset Object Scope of a Rowset Object Rowset Class Built-In Functions Row…

CUDA学习笔记(十二) CUDA库简介

CUDA Libraries简介 上图是CUDA 库的位置&#xff0c;本文简要介绍cuSPARSE、cuBLAS、cuFFT和cuRAND&#xff0c;之后会介绍OpenACC。 cuSPARSE线性代数库&#xff0c;主要针对稀疏矩阵之类的。cuBLAS是CUDA标准的线代库&#xff0c;不过没有专门针对稀疏矩阵的操作。cuFFT傅里…

YOLOv5算法改进(20)— 如何去写YOLOv5相关的论文(包括论文阅读+规律总结+写作方法)

前言:Hello大家好,我是小哥谈。最近一直在阅读关于YOLOv5的相关论文,读着读着我发现一条可以发论文的规律,特此简单总结一下,希望能够对同学们有所启迪!🌈 前期回顾: YOLOv5算法改进(1)— 如何去改进YOLOv5算法