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

目录

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 {// 创建 SelectorSelector 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.rhkb.cn/news/438280.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用Arduino单片机读取PCF8591模数转换器的模拟量并转化为数字输出

PCF8591是一款单芯片&#xff0c;单电源和低功耗8位CMOS数据采集设备。博文[1]对该产品已有介绍&#xff0c;此处不再赘述。但该博文是使用NVIDIA Jetson nano运行python读取输入PCF8591的模拟量的&#xff0c;读取的结果显示在屏幕上&#xff0c;或输出模拟量点亮灯。NVIDIA J…

计算机毕业设计 基于Python的智能文献管理系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

讯飞星火编排创建智能体学习(四):网页读取

目录 引言 网页读取节点 如何生成网址 测试 引言 在讯飞星火编排创建智能体学习&#xff08;三&#xff09;&#xff1a;搜索工具-CSDN博客中&#xff0c;我介绍了如何用搜索工具从网上搜索车次信息。不过&#xff0c;在测试中我们也发现讯飞星火的这个工具并不是特别完善&…

誉天Linux云计算课程学什么?为什么保障就业?

一个IT工程师相当于干了哪些职业? 其中置顶回答生动而形象地描绘道&#xff1a; 一个IT工程师宛如一个超级多面手&#xff0c;相当于——加班狂程序员测试工程师实施工程师网络工程师电工装卸工搬运工超人。 此中酸甜苦辣咸&#xff0c;相信很多小伙伴们都深有体会。除了典…

ESP01 AT指令学习

一 、AT指令 测试指令&#xff1a;ATCWMODE? 参数及取值范围 cwmode&#xff08;1-3&#xff09; 查询指令&#xff1a; ATCWMODE&#xff1f; 当前cwmode的取值 3 设置指令&#xff1a; ATCWMODE3 设置当前的cwmode为 3 1、station 模式 连接到其他wifi 2、softA…

Unity实战案例全解析:RTS游戏的框选和阵型功能(5)阵型功能 优化

前篇&#xff1a;Unity实战案例全解析&#xff1a;RTS游戏的框选和阵型功能&#xff08;4&#xff09;阵型功能-CSDN博客 本案例来源于unity唐老狮&#xff0c;有兴趣的小伙伴可以去泰克在线观看该课程 我只是对重要功能进行分析和做出笔记分享&#xff0c;并未无师自通&#x…

SpringBoot3+Druid YAML配置

背景 Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生&#xff0c;内置强大的监控功能&#xff0c;监控特性不影响性能。功能强大&#xff0c;能防SQL注入&#xff0c;内置Loging能诊断Hack应用行为。现在已经SpringBoot3&#xff0c;Druid的配置也需要随…

Yolov11项目实战1:道路缺陷检测系统设计【Python源码+数据集+运行演示】

一、项目背景 随着城市化进程的加速和交通网络的不断扩展&#xff0c;道路维护成为城市管理中的一个重要环节。道路缺陷&#xff08;如裂缝、坑洞、路面破损等&#xff09;不仅影响行车安全&#xff0c;还会增加车辆的磨损和维修成本。传统的道路缺陷检测方法主要依赖人工巡检…

HarmonyOS/OpenHarmony Audio 实现音频录制及播放功能

关键词&#xff1a;audio、音频录制、音频播放、权限申请、文件管理 在app的开发过程中时常会遇见一些需要播放一段音频或进行语音录制的场景&#xff0c;那么本期将介绍如何利用鸿蒙 audio 模块实现音频写入和播放的功能。本次依赖的是 ohos.multimedia.audio 音频管理模块&am…

前缀和算法详解

对于查询区间和的问题&#xff0c;可以预处理出来一个前缀和数组 dp&#xff0c;数组中存储的是从下标 0 的位置到当前位置的区间和&#xff0c;这样只需要通过前缀和数组就可以快速的求出指定区间的和了&#xff0c;例如求 l ~ r 区间的和&#xff0c;就可以之间使用 dp[l - 1…

河南做网站与SEO:如何提升搜索引擎排名

河南做网站与SEO&#xff1a;如何提升搜索引擎排名 在当今数字化时代&#xff0c;越来越多的企业意识到互联网的重要性&#xff0c;特别是在河南这样一个快速发展的地区&#xff0c;建立一个优秀的网站已经成为企业发展的必要条件。而在建立网站的同时&#xff0c;SEO&#xff…

Spring Gateway学习

系列文章目录 JavaSE基础知识、数据类型学习万年历项目代码逻辑训练习题代码逻辑训练习题方法、数组学习图书管理系统项目面向对象编程&#xff1a;封装、继承、多态学习封装继承多态习题常用类、包装类、异常处理机制学习集合学习IO流、多线程学习仓库管理系统JavaSE项目员工…

高性能防静电主轴4033 AC-ESD 在线路板切割中的非凡表现

随着电子产品的日益小型化/集成化&#xff0c;线路板的制造也面临着更高的挑战。线路板分板作为电子制造流程中的关键环节&#xff0c;其效率和精度直接影响到最终产品的质量和市场竞争力。因此专用的高性能防静电主轴SycoTec 4033 AC-ESD凭借其卓越的性能&#xff0c;成为众多…

笔记本电脑怎么多选删除文件?误删除文件怎么办

在日常使用笔记本电脑中&#xff0c;我们可能会遇到需要删除大量文件的情况&#xff0c;例如清理临时文件、整理文档或卸载不再需要的程序。手动一个一个地删除不仅效率低下&#xff0c;还可能遗漏某些文件。那么&#xff0c;如何在笔记本电脑上高效地进行多选删除操作呢&#…

15分钟学 Python 第33天 :函数式编程简介

Day 33: 函数式编程简介 1. 引言 函数式编程是一种程序设计范式&#xff0c;它将计算视为数学函数的求值&#xff0c;避免了程序中的可变状态和副作用。Python虽然是一种多范式语言&#xff08;支持命令式、面向对象和函数式编程&#xff09;&#xff0c;但其函数式编程的特性…

WPF之UI进阶--控件样式与样式模板及词典

WPF的优势之一就是能够更加容易快捷的对窗体和控件的外面进行改造&#xff0c;换句话说&#xff0c;那就是UI设计个性化更加容易。主要是借助了样式、模板及词典来实现的。那么本篇博文就一一对他们进行介绍。 文章目录 一、样式1: 定义样式2: 使用Setter设置属性关于Property和…

CSS3--美开二度

免责声明&#xff1a;本文仅做分享&#xff01; 目录 定位 相对定位 绝对定位 定位居中 固定定位 堆叠层级 z-index 定位-小结 CSS 精灵 京东案例 字体图标 下载字体 使用字体 上传矢量图 CSS 修饰属性 垂直对齐方式 vertical-align 过渡 transition 透明度 opa…

二、kafka生产与消费全流程

一、使用java代码生产、消费消息 1、生产者 package com.allwe.client.simple;import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.pr…

C# 游戏引擎中的协程

前言 书接上回&#xff0c;我谈到了Unity中的协程的重要性&#xff0c;虽然协程不是游戏开发“必要的”&#xff0c;但是它可以在很多地方发挥优势。 为了在Godot找回熟悉的Unity协程开发手感&#xff0c;不得不自己做一个协程系统&#xff0c;幸运的是&#xff0c;有了Unity的…

TI DSP TMS320F280025 Note15:串口SCI的使用

TMS320F280025 串口SCI的使用 ` 文章目录 TMS320F280025 串口SCI的使用框图分析串口特点可编程数据格式SCI端口中断非FIFO/FIFO模式下SCI中断的操作/配置UartDriver.cUartDriver.h串口时钟由PCLKCR7控制使能,默认位系统时钟4分频 串口接收与发送都可以触发中断 串口使用的引脚…