【NIO基础】NIO(非阻塞 I/O)和 IO(传统 I/O)的区别,以及 NIO 的三大组件详解

news2024/11/25 1:33:10

目录

1、NIO

2、NIO 和 IO 的区别

1. 阻塞 vs 非阻塞

2. 一个线程 vs 多个连接

3. 面向流 vs 面向缓冲

4. 多路复用

3、Channel & Buffer

(1)Channel:双向通道

(2)Buffer:缓冲区

(3)ByteBuffer(通用的Buffer)

(一)ByteBuffer 正确使用方法

(二)ByteBuffer 结构

(三)ByteBuffer 分配空间

 (四)ByteBuffer 中数据的读和写​​​​​

4、Selector

(1)Selector 的工作原理

(2)register() 方法详解

(3)selectedKeys() 方法详解

(4)为什么 Channel 必须设置为非阻塞模式

1、NIO

        NIONew I/O 的缩写,意思就是“新型输入输出”,它是在 Java 1.4 版本里加进来的,中文可以叫“新 I/O”,也叫“非阻塞 I/O”。NIO 其实就是对传统的阻塞 IO 做了个加强版,专门为了解决以前处理大量数据或者很多并发连接时性能不够好的问题。

NIO 的牛掰之处在于,它提供了全新的一套操作数据的方式,比如:

  • 非阻塞模式:你可以发起 IO 操作而不用等着它完成,干别的事也行。
  • 多路复用(Selector):一个线程能管理好多个连接,不需要每个连接都分配一个线程。
  • 缓冲区读写(Buffer):NIO 用缓冲区来存数据,读写的时候可以灵活处理,不用一次性读完或写完。

NIO 主要有三个核心组件

  1. 缓冲区(Buffer):就像个数据临时仓库,所有读写数据的操作都要经过它来处理。
  2. 通道(Channel):有点像以前的输入输出流,但它可以非阻塞地传数据,不用傻等。
  3. 选择器(Selector):这玩意儿相当于一个“事件监听器”,可以监听多个连接的事件(比如有数据可读、可写等),然后让线程去处理,这样一个线程就能同时管好多连接。

        NIO 让程序处理网络通信文件操作更加高效,尤其是当你要处理很多并发连接的时候(比如高并发服务器),它表现得非常出色。说白了,NIO 就是让你写的程序跑得更快、更省资源,特别适合那些要处理大量网络连接或者数据传输的场景。

2、NIO 和 IO 的区别

        在 Java 里,NIO(New I/O)和传统的 IO 最大的区别就在于如何处理数据如何利用系统资源。

1. 阻塞 vs 非阻塞

  • 传统 IO(阻塞 IO):如果你用传统的 IO 去读取数据,就像你打电话给外卖员问订单进展,直到对方告诉你信息之前,你只能一直举着电话不能干别的。也就是说,线程在等待 I/O 操作完成时,会被 卡住,要等到数据读完或写完才能继续干其他事。

  • NIO(非阻塞 IO):你打电话给外卖员后,他可能说"稍等,我还在路上",然后你就可以继续干别的事,比如刷个视频,再过一会儿再去看看外卖有没有到。这时,线程不会因为等待而停下来,你可以处理其他任务,再定期看看数据准备好了没有。不会因为没有数据而一直卡在那里。

2. 一个线程 vs 多个连接

  • 传统 IO:每个网络连接都会单独给你开一个线程,线程就像一个专职服务员,每个客人(连接)都配一个服务员。问题来了:如果餐厅里客人太多,光请服务员就累死老板了。线程多了,系统的性能也会受到影响,因为线程多了之后管理它们、切换任务等会让系统变慢。

  • NIO:NIO 就聪明多了,它就像一个服务员可以同时服务很多桌子(连接),当某桌的客人喊服务时,服务员才会过去处理。这样,只用少量的线程就能服务大量的网络连接,减少了资源的浪费。

3. 面向流 vs 面向缓冲

  • 传统 IO:数据是 按流(Stream)处理 的,意思就是你每次只能顺序处理数据,一点一点从头看到尾,就像看电视时从头开始看,不能快进。

  • NIO:NIO 用的是 缓冲区(Buffer),就像在看视频时你可以拖动进度条去看想看的片段。你可以先把数据放到缓冲区里,然后根据需要随时读写,不需要按照固定顺序来。

4. 多路复用

  • 传统 IO:每个连接(比如一个客户端连接到服务器)都是一对一的,没法让一个线程同时处理多个连接。

  • NIO:NIO 有个“多路复用器”(Selector),它就像一个大屏幕显示所有的订单状态,服务员可以随时查看哪个订单状态有变化(比如哪个客户数据准备好了),然后再去处理,这样就不用每个订单派一个人盯着了。

3、Channel & Buffer

(1)Channel:双向通道

        想象一下,Channel 就像一条双向的水管,水可以从管道里流进来,也可以流出去。在 NIO 中,Channel 也是这样,可以用来读数据(从 Channel 里往 Buffer 里流)和写数据(把 Buffer 里的数据流到 Channel 里)。和传统的输入输出流(Stream)相比,Channel 的功能更强大,因为 Stream 要么是读数据,要么是写数据,而 Channel 可以同时进行这两项操作。

常见的 Channel 类型:

  • FileChannel:用来操作文件的通道,可以读写文件数据。
  • DatagramChannel:用于 UDP 网络通信的通道,适合需要快速传输小数据包的场景。
  • SocketChannel:用于 TCP 网络通信的通道,保证数据的可靠性,适合大多数网络应用。
  • ServerSocketChannel:专门用于处理服务器端的 Socket 连接,能够接收来自客户端的连接请求。

这里有个简单的示意图,展示了 Channel 和 Buffer 之间的关系:

(2)Buffer:缓冲区

        Buffer 就是用来临时存储数据的容器,可以理解为一个数据仓库。在读写数据的时候,数据首先会放到 Buffer 里,再从 Buffer 进行操作。Buffer 的好处是让数据处理变得更加高效,因为可以一次性读写一大块数据,而不是每次都一个字节一个字节地处理。

常见的 Buffer 类型:

  • ByteBuffer:处理字节数据的缓冲区,可以直接用来读写文件和网络数据。
    • MappedByteBuffer:将文件的某部分映射到内存,可以高效地读写大文件。
    • DirectByteBuffer:直接在内存中分配,不会受 Java 堆的限制,适合高性能应用。
    • HeapByteBuffer:在 Java 堆内存中分配,普通使用,性能稍逊色。
  • ShortBuffer:处理短整型数据的缓冲区。
  • IntBuffer:处理整型数据的缓冲区。
  • LongBuffer:处理长整型数据的缓冲区。
  • FloatBuffer:处理浮点型数据的缓冲区。
  • DoubleBuffer:处理双精度浮点型数据的缓冲区。
  • CharBuffer:处理字符数据的缓冲区。

其中,ByteBuffer 是一个通用且灵活的选择,适用于大多数应用场景。如果在特定场景下遇到性能瓶颈或有特殊需求,再考虑使用其他类型的 Buffer(如 MappedByteBufferDirectByteBuffer)。

(3)ByteBuffer(通用的Buffer)

(一)ByteBuffer 正确使用方法

        当你在使用 NIO 里的 ByteBuffer 时,操作起来可能有些步骤需要特别注意,尤其是在读写数据的过程中。让我们用简单的步骤来介绍一下:

  1. 写数据到 buffer:首先,我们需要往 ByteBuffer 里写数据,比如通过 channel.read(buffer)
  2. 切换为读模式:写完后要告诉 ByteBuffer,我们现在要读数据了。为此,需要调用 flip(),这一步相当于把写好的内容准备好给我们读取。
  3. 从 buffer 读数据:我们可以用 buffer.get() 来读取数据。
  4. 切换回写模式:当我们想再次写入新的数据时,要让 ByteBuffer 回到写模式,可以调用 clear()compact(),重新开始写入新的数据。
  5. 重复以上步骤:通常我们会重复这几步来处理 I/O 操作。
(二)ByteBuffer 结构

        为了更好地理解 ByteBuffer,我们需要了解它的三个核心属性:capacitypositionlimit。这些属性是掌握 ByteBuffer 读写的关键。

  • capacity:容量,表示这个 buffer 最多可以容纳多少数据。
  • position:当前读写的位置。在写模式下,position 表示下一个写入数据的位置;在读模式下,position 表示下一个要读取的位置。
  • limit:写模式下,limit 通常等于 capacity,表示可以写入的最大位置;读模式下,limit 表示你可以读到的最后一个位置,防止越界读取。

        1.初始阶段:当我们新建一个 ByteBuffer 时,position 从 0 开始,limit 等于 capacity。也就是说,缓冲区可以从头开始写入数据,最多写满整个容量。

        2.写模式:我们可以往 ByteBuffer 中写入数据,比如我们写了 4 个字节,position 就会移动到第 4 个字节的位置,而 limit 依然是 capacity

        3.切换为读模式:调用 flip() 后,position 会切换为 0,表示准备从头开始读取数据,limit 切换到我们最后写入的位置,防止我们读到还没有写的数据。

        4.读数据:当我们读取数据时,position 会随着我们读取的字节数移动,直到达到 limit 为止。

        5.清空缓冲区:当我们调用 clear() 后,ByteBuffer 又回到最初的写模式状态,position 归零,limit 回到 capacity

        6.使用 compact():如果我们没有读完所有的数据,但又想往缓冲区写入新数据,可以用 compact()。它会把未读的数据移到缓冲区的开始位置,然后把 position 移到未读数据之后的位置,方便我们继续写入。

(三)ByteBuffer 分配空间

        在 NIO 中,ByteBuffer 有两种常见的分配方式:一种是通过堆内存(Heap Buffer)分配,另一种是通过直接内存(Direct Buffer)分配,两种方式各有优缺点。

1. 堆内存分配(Heap Buffer)

        这是最常用、最简单的一种方式,直接从 JVM 的堆中分配内存。通过 ByteBuffer.allocate() 方法实现。

ByteBuffer heapBuffer = ByteBuffer.allocate(16);

这种方式创建的缓冲区是基于堆内存的,JVM 可以直接管理这些内存。堆缓冲区有以下特点:

  • 读写性能:因为数据在堆中,访问速度较快,但是当进行 I/O 操作时,数据可能需要从堆内存复制到内核空间的 I/O 缓冲区,所以对于 I/O 密集型操作来说效率稍低。
  • 垃圾回收:缓冲区的生命周期由 JVM 管理,垃圾回收器可以自动清理不再使用的缓冲区。这也意味着频繁创建和销毁堆缓冲区可能会导致更频繁的垃圾回收(GC),影响性能。

2. 直接内存分配(Direct Buffer)

        通过 ByteBuffer.allocateDirect() 方法,可以分配一个直接内存缓冲区,这种方式的缓冲区直接在操作系统的内存中分配,不经过 JVM 的堆。

ByteBuffer directBuffer = ByteBuffer.allocateDirect(16);

直接缓冲区的特点是:

  • I/O 性能:由于数据直接分配在操作系统的内存中,在进行 I/O 操作时,不需要将数据从 JVM 堆内存复制到内核空间且不受垃圾回收影响,从而提高了 I/O 性能。
  • 分配和释放成本高:因为直接内存是由操作系统分配和管理的,分配和释放的成本较高,且需要显式释放,否则可能出现内存泄漏(Java 的垃圾回收机制不能自动清理直接缓冲区)。
 (四)ByteBuffer 中数据的读和写​​​​​

1.写数据到 ByteBuffer

        准备空间:首先,我们需要为 ByteBuffer 分配空间,比如用 ByteBuffer.allocate(size)。这就像买了一个空箱子,决定它的大小。

ByteBuffer buffer = ByteBuffer.allocate(16);

        写入数据:接下来,我们可以用 put() 方法把数据放进这个缓冲区。记住,这个过程是在“写模式”下进行的,也就是说,你可以不断地将数据写入这个缓冲区,直到达到它的容量限制。

buffer.put((byte) 1); // 写入 1
buffer.put((byte) 2); // 写入 2

        切换到读模式:写完数据后,我们需要调用 flip() 方法来切换到“读模式”。这个方法会设置缓冲区的读取位置,也就是告诉缓冲区:接下来我要读取你里面的数据了。

buffer.flip(); // 切换到读模式

2.从 ByteBuffer 读取数据

        读取数据:一旦切换到读模式,就可以使用 get() 方法来读取数据。这个方法会从缓冲区的当前位置读取数据,并自动移动读取位置。

byte firstValue = buffer.get(); // 读取第一个值
byte secondValue = buffer.get(); // 读取第二个值

处理读取后的状态:在读取完数据后,缓冲区的 position 会向前移动,表示我们已经读取了这些数据。如果我们想再次写入新的数据,就需要调用 clear()compact() 方法。这两个方法在前面也有提到。

  • clear():这个方法会重置缓冲区,设置 position 为 0,limit 为容量,准备再次写入数据。但是这会清空已经读取的数据,所有内容都会被丢弃。
buffer.clear(); // 清空缓冲区,准备写入新数据
  • compact():这个方法会把未读取的部分(即还没被读的内容)移动到缓冲区的开始位置,然后再准备写入新数据。这种方式可以保留尚未读取的数据。
buffer.compact(); // 将未读数据压缩到前面,并准备写入新数据

3.读写数据总结: 

所以,ByteBuffer 的读和写过程大致可以归纳为:

  1. 分配空间:创建一个新的缓冲区。
  2. 写数据:用 put() 方法向缓冲区写入数据。
  3. 切换模式:使用 flip() 方法切换到读模式。
  4. 读数据:用 get() 方法读取数据。
  5. 清理或压缩:调用 clear()compact() 方法以便再次写入数据。

通过这种方式,我们就能高效地在 ByteBuffer 中读写数据,为 NIO 的性能优化奠定基础。

4、Selector

        在 NIO 中,Selector 是一个非常重要的组件,它的作用是让一个线程能够同时管理多个 Channel。Selector 可以被视作一个 大管家,管理多个 (Channel)。当有客人(事件)到访时,管家会及时通知你,让你去接待他们。这样,你就不需要为每个客人都派一个服务员(线程),从而节省资源。

(1)Selector 的工作原理

Selector 的工作流程通常包括以下几个步骤:

  1. 注册 Channel首先,你需要将要管理的 Channel 注册到 Selector 上,告诉 Selector 哪些 Channel 需要关注。
  2. 调用 select() 方法然后,调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个注册的 Channel 发生读写就绪事件。也就是说,Selector 会在这里“守门”,等待事件的发生。
  3. 处理事件一旦有 Channel 有事件发生(例如有数据可读或可写),select() 方法会返回这些事件,然后你可以在一个线程中处理所有的 Channel 事件。这种方式避免了因为某个 Channel 的事件而让线程被阻塞。

代码示例:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SelectableChannel;
import java.util.Iterator;

public class NioSelectorExample {
    public static void main(String[] args) throws IOException {
        // 创建 Selector
        Selector selector = Selector.open();

        // 创建 ServerSocketChannel,并设置为非阻塞模式
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        serverChannel.configureBlocking(false);

        // 将 serverChannel 注册到 Selector,监听接受连接事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("Server is listening on port 8080...");

        while (true) {
            // 阻塞等待事件发生
            selector.select();

            // 获取所有已准备好的事件
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();

            while (keys.hasNext()) {
                SelectionKey key = keys.next();
                keys.remove();

                if (key.isAcceptable()) {
                    // 接受新的连接
                    SocketChannel clientChannel = serverChannel.accept();
                    clientChannel.configureBlocking(false);
                    // 注册到 Selector,监听读取事件
                    clientChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("Accepted new connection from " + clientChannel.getRemoteAddress());
                } else if (key.isReadable()) {
                    // 读取数据
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(256);
                    int bytesRead = clientChannel.read(buffer);

                    if (bytesRead == -1) {
                        // 客户端关闭连接
                        clientChannel.close();
                        System.out.println("Closed connection from " + clientChannel.getRemoteAddress());
                    } else {
                        // 处理读取的数据
                        String message = new String(buffer.array()).trim();
                        System.out.println("Received: " + message);
                    }
                }
            }
        }
    }
}

        在这个示例中,我们主要关注 Channel的register() 方法、Selector的selectedKeys() 方法 和 为什么被注册到 Selector 中的 Channel 需要通过 configureBlocking(false) 方法设置为非阻塞的。

(2)register() 方法详解

作用register() 方法用于将一个 Channel 注册到 Selector。这使得 Selector 能够监视该 Channel 上的特定事件(如连接、读取或写入)。每个 Channel 在注册时可以指定一个或多个感兴趣的事件(如连接、读取或写入)。

方法签名:

public SelectionKey register(Selector sel, int ops) throws ClosedChannelException;

参数

  • Selector sel需要注册的 Selector。
  • int ops感兴趣的操作类型,例如 SelectionKey.OP_ACCEPT(接收连接)、SelectionKey.OP_READ(可读)、SelectionKey.OP_WRITE(可写)。

返回值:返回一个 SelectionKey,用于标识该 Channel 的注册状态。

(3)selectedKeys() 方法详解

作用selectedKeys() 方法返回一个 Set<SelectionKey>,包含了上一次调用 select() 方法后,所有已准备好的事件的 SelectionKey。它可以检查哪些 Channel 上发生了感兴趣的事件,并进行相应的处理。

返回值:返回的 Set<SelectionKey> 中包含了所有已准备好处理的 Key,开发者可以通过这些 Key 获取具体的 Channel 和事件类型。

注意:处理完一个 SelectionKey 后,必须手动从集合中移除它selectedKeys() 的集合不会自动移除已处理的 Key。如果不手动移除,下一次事件循环时,你将继续处理已完成的事件,可能导致重复处理。示例:调用remove()方法移除已处理的 SelectionKey

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();

while (iterator.hasNext()) {
    SelectionKey key = iterator.next();
    // 处理 key 的逻辑
    iterator.remove();  // 处理后手动移除
}

(4)为什么 Channel 必须设置为非阻塞模式

在 NIO 中,注册到 Selector 的 Channel 必须设置为非阻塞模式,这里有几个原因:

(一)避免线程阻塞:如果 Channel 是阻塞的,当调用 select() 方法时,如果某个 Channel 的 I/O 操作未准备好(例如,没有数据可读),则相关的线程会被阻塞,无法继续处理其他 Channel 的事件。这就违背了使用 Selector 的初衷。

(二)提高资源利用率:通过使用非阻塞模式,线程可以在等待 I/O 事件的同时处理其他任务。这样,CPU 资源得以更有效地利用,避免了线程因等待 I/O 操作而闲置。

(三)单线程管理多个 Channel:NIO 的设计初衷是让单个线程能够高效地管理多个 Channel。非阻塞模式使得线程可以在一个事件循环中处理多个 Channel 的状态变化,而不会被单个 Channel 的状态阻塞。

推荐:

【Redis】Redis中的 AOF(Append Only File)持久化机制-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/m0_65277261/article/details/142661193?spm=1001.2014.3001.5502【MySQL】常见的SQL优化方式(二)-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/m0_65277261/article/details/142610165?spm=1001.2014.3001.5502

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

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

相关文章

GO网络编程(五):海量用户通信系统3:整体框架与C/S通信总体流程【重要】

这个系统其实是尚硅谷的老韩讲的&#xff08;尚硅谷网络编程项目&#xff09;&#xff0c;但是他讲得很碎片化&#xff0c;思路不够清晰&#xff0c;时间又长&#xff0c;所以要掌握还是挺难的。如果你听了他的视频&#xff0c;不去梳理系统业务流程&#xff0c;不去看代码就往…

云计算身份认证与访问控制(Cloud Computing Identity Authentication and Access Control)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

支持向量机(SVM)基础教程

一、引言 支持向量机&#xff08;Support Vector Machine&#xff0c;简称SVM&#xff09;是一种高效的监督学习算法&#xff0c;广泛应用 于分类和回归分析。SVM以其强大的泛化能力、简洁的数学形式和优秀的分类效果而备受机器学 习领域的青睐。 二、SVM基本原理 2.1 最大间…

watch命令:周期执行指定命令

一、命令简介 ​watch ​命令用于周期性地执行指定的命令&#xff0c;并显示其输出结果。 ‍ 二、命令参数 2.1 命令格式 watch [选项] 命令2.2 选项 ​-n, --interval​: 指定更新间隔时间&#xff08;以秒为单位&#xff09;。默认间隔时间为 2 秒。​-d, --difference…

数学与生活

多学科交叉 信号处理 小波 经济 政策 计算机 统计 信号处理与市场分析 经济与数据分析 政策与统计 过去的数学家没有一个是纯粹的数学家&#xff1b;生活中各方面工程的&#xff0c;物理的&#xff0c;天文&#xff0c;地理的&#xff0c;赌博&#xff0c;政治的&#xff1b…

删除AlibabaProtect

首先管理员运行cmd 然后执行下行 sc delete AlibabaProtect重启电脑&#xff0c;再删除该文件夹C:\Program Files (x86)\AlibabaProtect

prometheus学习笔记之PromQL

prometheus学习笔记之PromQL 一、PromQL语句简介 官方文档&#xff1a;https://prometheus.io/docs/prometheus/latest/querying/basics/ Prometheus提供⼀个函数式的表达式语⾔PromQL (Prometheus Query Language)&#xff0c;可以使⽤户实时 地查找和聚合时间序列数据&…

vSAN04:vSAN远程数据存储挂载、双节点集群介绍/安装/组件读写/高级配置/故障处理方式

目录 vSAN远程数据存储挂载双节点vSAN集群介绍双节点vSAN集群安装双节点vSAN集群的组件读写方式双节点vSAN的高级配置双节点vSAN故障处理方式 vSAN远程数据存储挂载 在同一个vCenter下的VSAN集群可以互相挂载对方VSAN存储&#xff0c;以达到提高资源利用率的目的。 一个集群最…

Docker系列-5种方案超详细讲解docker数据存储持久化(volume,bind mounts,NFS等)

文章目录 Docker的数据持久化是什么&#xff1f;1.数据卷&#xff08;Data Volumes&#xff09;使用Docker 创建数据卷创建数据卷创建一个容器&#xff0c;将数据卷挂载到容器中的 /data 目录。进入容器&#xff0c;查看数据卷内容停止并重新启动容器&#xff0c;数据卷中的数据…

打卡第四天 P1081 [NOIP2012 提高组] 开车旅行

今天是我打卡第四天&#xff0c;做个省选/NOI−题吧(#^.^#) 原题链接&#xff1a;[NOIP2012 提高组] 开车旅行 - 洛谷 题目描述 输入格式 输出格式 输入输出样例 输入 #1 4 2 3 1 4 3 4 1 3 2 3 3 3 4 3 输出 #1 1 1 1 2 0 0 0 0 0 输入 #2 10 4 5 6 1 …

k8s 中存储之 hostPath 卷

目录 1 hostPath 卷介绍 2 hostPath 卷实际应用操作 2.1 创建 pod 资源类型 2.2 修改清单文件增加 hostPath 对应的参数配置 2.3 查看是否创建 卷 和 pod 2.4 创建发布文件测试是否正常访问 1 hostPath 卷介绍 EmptyDir中数据不会被持久化&#xff0c;它会随着Pod的结束而销…

每日一题|2187. 完成旅途的最少时间|二分法、计数器

本题的一个思路是从小到大遍历全部可能的t&#xff0c;并分别计算当前每一辆车所能够行驶的最多trips数量。 但是如果从1开始&#xff0c;结束在最不理想的情况是max(time) * totalTrip&#xff0c;在数据很大的时候很容易time out。 所以不妨对每一个可能的t重新思考性质。随…

STL的位图:bitset

引言 在C标准模板库&#xff08;STL&#xff09;中&#xff0c;bitset是一种用于表示固定大小序列的位集合的容器。每个位&#xff08;bit&#xff09;可以被独立地设置或清除&#xff0c;即它可以单独地表示0或1。bitset在处理二进制数据时非常有用&#xff0c;尤其是在需要节…

linux安装百度网盘

版本20.04 下载 deb&#xff1a;debian系列&#xff0c;Ubuntu下载这个 rpm&#xff1a;redhat系列 安装 在Downloads中找到&#xff0c;打开终端&#xff0c;使用命令安装 sudo dpkg -i 名称 //dpkg:Debian package在所有文件中找到。

Linux相关概念和易错知识点(12)(命令行参数、环境变量、本地变量)

1.命令行参数 &#xff08;1&#xff09;main函数的参数int argc和char* argv[]是什么&#xff1f; main函数可以带参数&#xff0c;即int main(int argc, char* argv[])&#xff0c;(int argc, char* argv[])叫做命令行参数列表&#xff0c;int argc叫参数的个数&a…

【YOLO学习】YOLOv3详解

文章目录 1. 网络结构1.1 结构介绍1.2 改进 2. 训练与测试过程3. 总结 1. 网络结构 1.1 结构介绍 1. 与 YOLOv2 不同的是&#xff0c;YOLOv3 在 Darknet-19 里加入了 ResNet 残差连接&#xff0c;改进之后的模型叫 Darknet-53。在 ImageNet上 实验发现 Darknet-53 相对于 ResN…

VSCODE驯服日记(三):配置C++环境

1. 下载mingw64&#xff0c;解压后把bin并添加到环境变量 1>编译器介绍 mingw&#xff1a;专为windowsgcc&#xff1a;多平台msvc &#xff1a;windows&#xff0c;且配合vs使用更佳 注意与调试器gdb和lldb的区别 2. 安装vscode插件&#xff1a; 安装C/C插件 安装code ru…

力扣之1322.广告效果

题目&#xff1a; sql建表语句&#xff1a; Create table If Not Exists Ads (ad_id int,user_id int,action ENUM (Clicked, Viewed, Ignored) ); Truncate table Ads; insert into Ads (ad_id, user_id, action) values (1, 1, Clicked); insert into Ads (ad_id, use…

Sublime Text 下载地址分享

Sublime Text官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘123云盘为您提供Sublime Text最新版正式版官方版绿色版下载,Sublime Text安卓版手机版apk免费下载安装到手机,支持电脑端一键快捷安装https://www.123684.com/s/kPxoTd-dCnxHSublime Text官方版下载丨最新版下…

双十一可以买什么物品?重磅推荐五款好用品牌!

距离今年的双十一盛典仅剩数十日&#xff0c;您是否已将心爱商品添加至购物车中了呢&#xff1f;还在犹豫未满载的朋友也无需焦虑&#xff0c;特意为您精选了五款好用的宝贝推荐&#xff0c;旨在为您的购物清单增添几分灵感与便捷&#xff0c;期待能为您的双十一购物之旅增添一…