当前位置: 首页 > news >正文

Java网络编程演进:从NIO到Netty的UDP实践全解析

前言

在当前高并发、大数据量的互联网环境下,高性能的网络通信框架变得越来越重要。本文将深入探讨Java网络编程的演进,从NIO到Netty,并通过实际案例分析Netty的优势和应用。(本次主要以UDP请求为例)

Java网络编程演进

传统的BIO(Blocking I/O)

  • 每个连接一个线程
  • 适用于连接数较少的场景
  • 资源消耗大,连接数增加时性能下降明显

NIO(Non-blocking I/O)

  • 引入了Channel、Buffer、Selector的概念
  • 支持非阻塞I/O操作
  • 可以用少量线程处理大量连接
  • 编程模型复杂,开发难度大

Netty

  • 基于NIO的异步事件驱动框架
  • 简化了NIO编程模型
  • 提供了丰富的协议支持和工具类
  • 高性能、高可扩展性

比较

1. 传统NIO vs Netty

特性传统NIONetty
编程复杂度较高较低
代码可维护性较差较好
内存管理手动管理自动管理(ByteBuf)
线程模型需自行实现已封装完善
处理粘包/拆包需自行实现提供多种编解码器

2. Tomcat vs Netty

特性TomcatNetty
定位Web容器网络应用框架
协议支持主要HTTP多协议支持
性能中等
使用场景Web应用通用网络服务

核心组件

1. Channel

Channel是Netty网络操作抽象类,包含了基本的I/O操作。

2. EventLoop

EventLoop负责处理注册到其上的Channel的所有I/O操作。

3. ChannelPipeline

ChannelPipeline提供了ChannelHandler链的容器,负责处理或拦截Channel的入站事件和出站操作。

网络协议支持

Netty支持多种协议,包括TCP和UDP。本文将重点介绍UDP协议的实现。

Maven依赖如下

        <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.100.Final</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.35</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>

    1. NIO实现

    1.1 服务端代码

    NIO的UDP实现相对复杂,需要手动处理以下方面:

    1. 创建和配置DatagramChannel
    2. 设置非阻塞模式
    3. 创建Selector并注册Channel
    4. 实现事件循环来处理I/O事件
    5. 手动管理ByteBuffer
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.DatagramChannel;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.charset.StandardCharsets;
    import java.util.Iterator;
    import java.util.Scanner;
    import java.util.concurrent.ConcurrentHashMap;public class NioUdpServer {private static final int SERVER_PORT = 8080;private DatagramChannel datagramChannel;private Selector selector;private final ByteBuffer buffer = ByteBuffer.allocate(1024);private final ConcurrentHashMap<String, InetSocketAddress> clientAddresses = new ConcurrentHashMap<>();private volatile boolean running = true;public void start() throws IOException {// 创建DatagramChannel并配置为非阻塞模式datagramChannel = DatagramChannel.open();datagramChannel.configureBlocking(false);datagramChannel.bind(new InetSocketAddress(SERVER_PORT));// 创建Selector并注册channelselector = Selector.open();datagramChannel.register(selector, SelectionKey.OP_READ);System.out.println("NIO UDP Server started on port " + SERVER_PORT);// 启动发送消息的线程Thread senderThread = new Thread(this::handleUserInput);senderThread.start();// 主线程处理接收消息while (running) {try {if (selector.select() > 0) {Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isReadable()) {handleRead(key);}}}} catch (IOException e) {e.printStackTrace();}}}private void handleRead(SelectionKey key) throws IOException {DatagramChannel channel = (DatagramChannel) key.channel();buffer.clear();InetSocketAddress clientAddress = (InetSocketAddress) channel.receive(buffer);if (clientAddress != null) {buffer.flip();String message = StandardCharsets.UTF_8.decode(buffer).toString();System.out.println("Received from " + clientAddress + ": " + message);// 保存客户端地址clientAddresses.put(clientAddress.toString(), clientAddress);// 发送确认消息String response = "Server received: " + message;sendMessage(response, clientAddress);}}private void handleUserInput() {Scanner scanner = new Scanner(System.in);while (running) {System.out.print("Enter message to broadcast (type 'exit' to quit): ");String message = scanner.nextLine();if ("exit".equalsIgnoreCase(message)) {running = false;break;}// 广播消息给所有已知客户端broadcastMessage(message);}scanner.close();}private void broadcastMessage(String message) {for (InetSocketAddress clientAddress : clientAddresses.values()) {sendMessage(message, clientAddress);}}private void sendMessage(String message, InetSocketAddress address) {try {ByteBuffer buffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));datagramChannel.send(buffer, address);System.out.println("Sent to " + address + ": " + message);} catch (IOException e) {e.printStackTrace();}}public void stop() {running = false;try {if (selector != null) {selector.close();}if (datagramChannel != null) {datagramChannel.close();}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {try {new NioUdpServer().start();} catch (IOException e) {e.printStackTrace();}}
    }
    

    1.2 NIO实现服务端代码解析
    (1) 通道配置
    datagramChannel = DatagramChannel.open();
    datagramChannel.configureBlocking(false);
    datagramChannel.bind(new InetSocketAddress(SERVER_PORT));

    解析:

    • 手动创建 DatagramChannel,用于 UDP 传输

    • 设置非阻塞模式,提高并发处理能力

    • 显式绑定端口,监听 UDP 请求


    (2) 选择器管理
    selector = Selector.open();
    datagramChannel.register(selector, SelectionKey.OP_READ);

    解析:

    • 手动创建 Selector,用于监听多个通道

    • 注册 DatagramChannelSelector,监听 OP_READ 事件


    (3) 消息处理
    private void handleRead(SelectionKey key) throws IOException {DatagramChannel channel = (DatagramChannel) key.channel();buffer.clear();InetSocketAddress clientAddress = (InetSocketAddress) channel.receive(buffer);// 处理消息
    }

    解析:

    • 手动管理 ByteBuffer,需 flip() 切换读写模式

    • 显式处理 receive() 解析数据

    • 必须手动清理 buffer,避免数据残留

    1.3 客户端代码
    import lombok.extern.slf4j.Slf4j;
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.DatagramChannel;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.charset.StandardCharsets;
    import java.util.Iterator;
    import java.util.Scanner;@Slf4j
    public class NioUdpClient {private static final String SERVER_HOST = "127.0.0.1";private static final int SERVER_PORT = 8080;private static final int CLIENT_PORT = 9090;private DatagramChannel datagramChannel;private Selector selector;private final ByteBuffer buffer = ByteBuffer.allocate(1024);private volatile boolean running = true;public void start() throws IOException {// 创建DatagramChannel并配置为非阻塞模式datagramChannel = DatagramChannel.open();datagramChannel.configureBlocking(false);datagramChannel.bind(new InetSocketAddress(CLIENT_PORT));// 创建Selector并注册channelselector = Selector.open();datagramChannel.register(selector, SelectionKey.OP_READ);log.info("NIO UDP Client 使用端口为 {}", CLIENT_PORT);// 启动发送消息的线程Thread senderThread = new Thread(this::handleUserInput);senderThread.start();// 主线程处理接收消息while (running) {try {if (selector.select() > 0) {Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isReadable()) {handleRead(key);}}}} catch (IOException e) {log.error("");}}}private void handleRead(SelectionKey key) throws IOException {DatagramChannel channel = (DatagramChannel) key.channel();buffer.clear();InetSocketAddress serverAddress = (InetSocketAddress) channel.receive(buffer);if (serverAddress != null) {buffer.flip();String message = StandardCharsets.UTF_8.decode(buffer).toString();log.info("从server接收到消息: {}", message);}}private void handleUserInput() {Scanner scanner = new Scanner(System.in);while (running) {log.info("输入消息并按回车发送 (type 'exit' to quit): ");String message = scanner.nextLine();if ("exit".equalsIgnoreCase(message)) {running = false;break;}sendMessage(message);}scanner.close();}private void sendMessage(String message) {try {ByteBuffer buffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));InetSocketAddress serverAddress = new InetSocketAddress(SERVER_HOST, SERVER_PORT);datagramChannel.send(buffer, serverAddress);log.info("Sent to server: {}", message);} catch (IOException e) {log.error("发送失败,{}",e.getMessage());}}public void stop() {running = false;try {if (selector != null) {selector.close();}if (datagramChannel != null) {datagramChannel.close();}} catch (IOException e) {log.error("关闭Selector或DatagramChannel失败,{}", e.getMessage());}}public static void main(String[] args) {try {new NioUdpClient().start();} catch (IOException e) {log.error("启动失败, {}", e.getMessage());}}
    }
    
    1.4 NIO实现客户端代码解析
    (1) 初始化
    datagramChannel = DatagramChannel.open();
    datagramChannel.configureBlocking(false);
    datagramChannel.bind(new InetSocketAddress(CLIENT_PORT));

    解析:

    • 需要手动配置通道
    • 显式设置客户端端口
    • 手动管理资源生命周期

    (2) 发送数据
    ByteBuffer buffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));
    InetSocketAddress serverAddress = new InetSocketAddress(SERVER_HOST, SERVER_PORT);
    datagramChannel.send(buffer, serverAddress);

    解析:

    • 手动管理 ByteBuffer

    • 显式指定目标地址

    • 同步发送消息

    2. Netty实现

    Netty的UDP实现简洁明了,主要涉及以下几个步骤:

    1. 配置Bootstrap
    2. 设置Channel类型为NioDatagramChannel
    3. 配置ChannelHandler来处理数据收发
    2.1 服务端代码
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioDatagramChannel;
    import io.netty.channel.socket.DatagramPacket;
    import io.netty.buffer.ByteBuf;
    import io.netty.util.CharsetUtil;
    import lombok.extern.slf4j.Slf4j;import java.net.InetSocketAddress;
    @Slf4j
    public class NettyUdpServer {public static void main(String[] args) throws InterruptedException {// 创建事件循环组,负责处理 I/O 操作EventLoopGroup group = new NioEventLoopGroup();try {// 创建引导程序,用于设置服务器参数Bootstrap b = new Bootstrap();b.group(group)// 设置通道类型为 NioDatagramChannel,适用于 UDP.channel(NioDatagramChannel.class)// 设置处理器.handler(new SimpleChannelInboundHandler<DatagramPacket>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {// 获取客户端地址InetSocketAddress sender = packet.sender();// 获取接收到的数据ByteBuf buf = packet.content();String received = buf.toString(CharsetUtil.UTF_8);log.info("收到来自 {} 的消息: {}", sender, received);// 回复客户端String response = "消息已收到: " + received;ByteBuf responseBuf = ctx.alloc().buffer();responseBuf.writeBytes(response.getBytes(CharsetUtil.UTF_8));ctx.writeAndFlush(new DatagramPacket(responseBuf, sender));}});// 绑定端口并启动服务器ChannelFuture f = b.bind(8080).sync();log.info("UDP 服务器已启动,监听端口 8080");// 等待服务器通道关闭f.channel().closeFuture().await();} finally {// 关闭事件循环组group.shutdownGracefully();}}
    }
    
    2.2 Netty实现服务端代码解析
    (1) Bootstrap 配置
    Bootstrap b = new Bootstrap();
    b.group(group).channel(NioDatagramChannel.class).handler(new SimpleChannelInboundHandler<DatagramPacket>()

    解析:

    • 使用 Bootstrap 而不是 ServerBootstrap,因为 UDP 是无连接的协议

    • 使用 NioDatagramChannel 作为通道类型,专门用于 UDP 通信

    • 通过 handler 处理数据包,定义如何解析和处理接收的数据

    (2) 消息处理流程
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {InetSocketAddress sender = packet.sender();ByteBuf buf = packet.content();String received = buf.toString(CharsetUtil.UTF_8);// 处理消息并回复
    }

    解析:

    • 通过 DatagramPacket 封装 UDP 数据包

    • 使用 packet.sender() 获取发送方地址

    • ByteBuf 处理二进制数据(Netty 提供的高效缓冲区)

    • 自动释放 ByteBuf,避免手动管理内存

    2.3 客户端代码
    import io.netty.bootstrap.Bootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.DatagramPacket;
    import io.netty.channel.socket.nio.NioDatagramChannel;
    import io.netty.util.CharsetUtil;import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.InetSocketAddress;import lombok.AllArgsConstructor;
    import lombok.extern.slf4j.Slf4j;@Slf4j
    @AllArgsConstructor
    public class NettyUdpClient {private final String host;private final int port;private Channel channel;public void run() throws Exception {// 创建事件循环组EventLoopGroup group = new NioEventLoopGroup();try {// 创建客户端引导程序Bootstrap b = new Bootstrap();b.group(group)// 设置通道类型为 NioDatagramChannel.channel(NioDatagramChannel.class)// 设置处理器.handler(new SimpleChannelInboundHandler<DatagramPacket>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {// 接收并打印服务器的响应String response = packet.content().toString(CharsetUtil.UTF_8);log.info("收到服务器响应:{}", response);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {log.error("客户端异常:", cause);}});// 绑定随机端口并等待channel = b.bind(0).sync().channel();log.info("UDP 客户端已启动,准备发送消息...");// 从控制台读取输入并发送BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));// 启动一个循环,不断从控制台读取输入while (true) {System.out.print("请输入要发送的消息(输入'exit'退出):");String message = reader.readLine();// 如果输入exit则退出if ("exit".equalsIgnoreCase(message)) {log.info("客户端即将关闭...");break;}// 创建UDP数据包并发送ByteBuf buf = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);DatagramPacket packet = new DatagramPacket(buf, new InetSocketAddress(host, port));// 发送消息channel.writeAndFlush(packet).addListener((ChannelFutureListener) future -> {if (future.isSuccess()) {log.info("消息发送成功");} else {log.error("消息发送失败:{}", future.cause().getMessage());}});}// 关闭连接if (channel != null) {channel.close().sync();}} finally {// 优雅关闭事件循环组group.shutdownGracefully();}}public static void main(String[] args) throws Exception {// 默认连接本地8080端口String host = "127.0.0.1";int port = 8080;new NettyUdpClient(host, port, null).run();}
    }
    

    2.4 Netty实现客户端代码解析
    (1) 初始化配置
    Bootstrap b = new Bootstrap();
    b.group(group).channel(NioDatagramChannel.class).handler(new SimpleChannelInboundHandler<DatagramPacket>()

    解析:

    • 与服务端类似,使用 NioDatagramChannel 处理 UDP 通信

    • 客户端不需要绑定端口,可以随机使用本地端口发送数据


    (2) 消息发送
    ByteBuf buf = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);
    DatagramPacket packet = new DatagramPacket(buf, new InetSocketAddress(host, port));
    channel.writeAndFlush(packet)

    解析:

    • 使用 Unpooled.copiedBuffer 创建数据缓冲区

    • DatagramPacket 封装目标地址

    • writeAndFlush 异步发送数据

    2.5 Netty小结特点
    1. 支持异步发送确认
    2. 灵活的消息收发处理
    3. 优雅的关闭机制
    4. 完善的异常处理

    Netty实现UDP双向通信

    服务端代码
    package com.lps.netty;import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioDatagramChannel;
    import io.netty.channel.socket.DatagramPacket;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.util.CharsetUtil;
    import lombok.extern.slf4j.Slf4j;import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.InetSocketAddress;
    import java.util.HashSet;
    import java.util.Set;@Slf4j
    public class NettyUdpBidirectionalServer {private static final int PORT = 8080;private static final Set<InetSocketAddress> clients = new HashSet<>();private static Channel serverChannel;public static void main(String[] args) throws Exception {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioDatagramChannel.class).handler(new SimpleChannelInboundHandler<DatagramPacket>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {InetSocketAddress sender = packet.sender();clients.add(sender); // 记录客户端地址ByteBuf buf = packet.content();String received = buf.toString(CharsetUtil.UTF_8);log.info("【服务器】收到来自 {} 的消息: {}", sender, received);// 回复客户端String response = "服务器已收到: " + received;ByteBuf responseBuf = Unpooled.copiedBuffer(response, CharsetUtil.UTF_8);ctx.writeAndFlush(new DatagramPacket(responseBuf, sender));}});serverChannel = b.bind(PORT).sync().channel();log.info("【服务器】UDP 服务器已启动,监听端口 {}", PORT);// 服务器主动向客户端发送消息BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));while (true) {System.out.print("【服务器】请输入要发送的消息 (输入exit退出):");String message = reader.readLine();if ("exit".equalsIgnoreCase(message)) {break;}sendMessageToClients(message);}} finally {group.shutdownGracefully();}}// 服务器主动向所有已知客户端发送消息private static void sendMessageToClients(String message) {if (serverChannel == null || clients.isEmpty()) {log.warn("【服务器】没有可发送的客户端");return;}ByteBuf buf = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);for (InetSocketAddress client : clients) {serverChannel.writeAndFlush(new DatagramPacket(buf.retainedDuplicate(), client));log.info("【服务器】向 {} 发送消息: {}", client, message);}}
    }
    

    客户端代码
    package com.lps.netty;import cn.hutool.core.util.RandomUtil;
    import io.netty.bootstrap.Bootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.DatagramPacket;
    import io.netty.channel.socket.nio.NioDatagramChannel;
    import io.netty.util.CharsetUtil;
    import lombok.extern.slf4j.Slf4j;import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.InetSocketAddress;@Slf4jpublic class NettyUdpBidirectionalClient {private static final String SERVER_HOST = "127.0.0.1";private static final int SERVER_PORT = 8080;private static final int CLIENT_PORT = 9090; // 客户端固定端口private Channel clientChannel;public static void main(String[] args) throws Exception {new NettyUdpBidirectionalClient().run();}public void run() throws Exception {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioDatagramChannel.class).handler(new SimpleChannelInboundHandler<DatagramPacket>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {String response = packet.content().toString(CharsetUtil.UTF_8);log.info("【客户端】收到服务器的消息: {}", response);}});clientChannel = b.bind(RandomUtil.randomInt(9000,10001)).sync().channel();log.info("【客户端】UDP 客户端已启动,监听端口 {}", RandomUtil.randomInt(9000,10001));// 发送消息到服务器BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));while (true) {System.out.print("【客户端】请输入要发送的消息 (输入'exit'退出):");String message = reader.readLine();if ("exit".equalsIgnoreCase(message)) {break;}sendMessageToServer(message);}} finally {group.shutdownGracefully();}}// 发送消息到服务器private void sendMessageToServer(String message) {ByteBuf buf = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);DatagramPacket packet = new DatagramPacket(buf, new InetSocketAddress(SERVER_HOST, SERVER_PORT));clientChannel.writeAndFlush(packet);log.info("【客户端】发送消息到服务器: {}", message);}
    }
    

    核心类和方法解析

     Netty 核心类

    类名作用
    Bootstrap用于启动 Netty 客户端或无连接的服务端
    NioDatagramChannel适用于 UDP 的 Netty 通道
    DatagramPacketUDP 数据包封装

     NIO核心类

    类名作用
    DatagramChannelNIO 提供的 UDP 通道
    Selector多路复用器,监听多个通道
    ByteBufferNIO 的缓冲区管理

    Netty vs. NIO 对比分析

    特性NettyNIO
    代码复杂度
    内存管理自动手动
    异步处理内置支持需要自行实现
    异常处理统一完善需手动处理
    扩展性一般

    总结

    通过对比NIO和Netty的实现,我们可以清楚地看到Netty在简化网络编程、提高开发效率和性能方面的优势。Netty不仅封装了复杂的NIO操作,还提供了丰富的功能和优化措施,使得开发高性能网络应用变得更加容易。

    随着技术的不断发展,保持学习和实践的态度,将帮助我们在网络编程领域不断提升。无论是使用NIO还是Netty,核心都是要理解网络编程的基本原理。

    相关文章:

    Java网络编程演进:从NIO到Netty的UDP实践全解析

    前言 在当前高并发、大数据量的互联网环境下&#xff0c;高性能的网络通信框架变得越来越重要。本文将深入探讨Java网络编程的演进&#xff0c;从NIO到Netty&#xff0c;并通过实际案例分析Netty的优势和应用。&#xff08;本次主要以UDP请求为例&#xff09; Java网络编程演…...

    Linux系统中快速安装docker

    1 查看是否安装docker 要检查Ubuntu是否安装了Docker&#xff0c;可以使用以下几种方法&#xff1a; 方法1&#xff1a;使用 docker --version 命令 docker --version如果Docker已安装&#xff0c;输出会显示Docker的版本信息&#xff0c;例如&#xff1a; Docker version …...

    人工智能之数学基础:幂法和反幂法求特征值和特征向量

    本文重点 特征值和特征向量是矩阵的重要性质,我们前面学习了矩阵的正交分解,要想完成正交分解需要求出一个矩阵的特征值和特征向量。有的时候,我们只需要求出一个矩阵的最大的特征值以及矩阵的最小特征值,它们以及它们对应的特征向量具有特殊的含义,下面我们介绍两种方法…...

    数据结构 -- 树的应用(哈夫曼树和并查集)

    树的应用 哈夫曼树 带权路径长度 结点的权&#xff1a;有某种现实含义的数值&#xff08;如&#xff1a;表示结点的重要性等&#xff09; 结点的带权路径长度&#xff1a;从树的根到该结点的路径长度&#xff08;经过的边数&#xff09;与该结点上权值的乘积 树的带权路径…...

    游戏引擎学习第193天

    仓库:https://gitee.com/mrxiao_com/2d_game_4 回顾 我们昨天做了一些非常有趣的实验。在实验中&#xff0c;我们的目标是实现一个能够在运行时改变的编译时常量的概念。最开始&#xff0c;这个想法纯粹是出于一时的兴趣&#xff0c;觉得这应该是个很有意思的尝试。于是我们进…...

    数据结构每日一题day7(顺序表)★★★★★

    题目描述&#xff1a;从顺序表中删除其值在给定值s与t之间(包含s和 t&#xff0c;要求 s<t)的所有元素&#xff0c;若s或t不合理或顺序表为空&#xff0c;则返回 false&#xff0c;若执行成功则返回 true。 算法思想&#xff1a; 输入检查&#xff1a;若顺序表为空、指针为…...

    ACM模式常用方法总结(Java篇)

    文章目录 一、ACM输入输出模式二、重要语法2.1、导包2.2、读取数据2.3、判断是否有下一个数据2.4、输出2.5、关闭scanner2.6、易踩坑点 一、ACM输入输出模式 在力扣上编写代码时使用的是核心代码模式&#xff0c;如果在面试中遇到ACM模式就会比较迷茫&#xff1f;ACM模式要求你…...

    SpringCould微服务架构之Docker(6)

    容器的基本命令&#xff1a; 1. docker exec &#xff1a;进入容器执行命令 2. docker logs: -f 持续查看容器的运行日志 3. docker ps&#xff1a;查看所有运行的容器和状态 案例&#xff1a;创建运行一个容Nginx容器 docker run--name myNginx -p 80:80 -d nginx 命…...

    脑疾病分类的疑惑【7】一般FMRI数据都存储为什么格式?能不能给我用数据简单的描述一下FMRI是如何存储的?

    fMRI 数据通常以 NIfTI&#xff08;Neuroimaging Informatics Technology Initiative&#xff09; 格式存储&#xff0c;这是一种专为神经影像设计的开放标准格式。以下是简化说明和示例&#xff1a; 1. 常见fMRI数据格式 格式扩展名特点NIfTI.nii 或 .nii.gz最常用&#xff0…...

    DOM 加载函数

    DOM 加载函数 在Web开发中,DOM(文档对象模型)加载函数是一个核心概念。它指的是在页面加载过程中,浏览器如何处理和解析HTML文档,并创建相应的DOM树。本文将深入探讨DOM加载函数的作用、原理及其在Web开发中的应用。 引言 随着互联网的飞速发展,Web技术日新月异。DOM作…...

    [特殊字符]《Curve DAO 系统学习目录》

    本教程旨在系统学习 Curve DAO 项目的整体架构、核心机制、合约设计、治理逻辑与代币经济等内容&#xff0c;帮助开发者全面理解其设计理念及运作方式。 目录总览&#xff1a; 1. Curve 项目概览 • 1.1 Curve 是什么&#xff1f;主要解决什么问题&#xff1f; • 1.2 与其他…...

    webpack和vite之间的区别

    Webpack 和 Vite 都是现代前端开发中非常流行的构建工具&#xff0c;但它们的设计理念、工作原理以及适用场景都有所不同。以下是两者之间详细的对比说明&#xff1a; 1. 构建机制与速度 Webpack: Webpack 是一个通用的模块打包工具&#xff0c;它通过分析项目中的依赖关系图来…...

    《Operating System Concepts》阅读笔记:p495-p511

    《Operating System Concepts》学习第 44 天&#xff0c;p495-p511 总结&#xff0c;总计 17 页。 一、技术总结 1.cache (1)定义 A cache is a region of fast memory that holds copies of data. (2)cache 和 buffer 的区别 The difference between a buffer and a cac…...

    Java进阶——位运算

    位运算直接操作二进制位&#xff0c;在处理底层数据、加密算法、图像处理等领域具有高效性能和效率。本文将深入探讨Java中的位运算。 本文目录 一、位运算简介1. 与运算2. 或运算异或运算取反运算左移运算右移运算无符号右移运算 二、位运算的实际应用1. 权限管理2. 交换两个变…...

    特征增强金字塔FPN

    特征增强金字塔FPN 利用 ConvNet 特征层次结构的金字塔形状&#xff0c;构建一个在所有尺度上都具有强大语义的特征金字塔 总结&#xff1a;特征金字塔是检测不同尺度物体的识别系统中的基本组成部分。 1.利用深度卷积网络固有的多尺度、金字塔层次结构&#xff0c;以边际额…...

    Java课程设计(双人对战游戏)持续更新......

    少废话&#xff0c;当然借助了ai&#xff0c;就这么个实力&#xff0c;后续会逐渐完善...... 考虑添加以下功能&#xff1a; 选将&#xff0c;选图&#xff0c;技能&#xff0c;天赋&#xff0c;道具&#xff0c;防反&#xff0c;反重力&#xff0c;物理反弹&#xff0c;击落…...

    c++第三课(基础c)

    1.前文 2.break 3.continue 4.return 0 1.前文 上次写文章到现在&#xff0c;有足足这么多天&#xff08;我也不知道&#xff0c;自己去数吧&#xff09; 开始吧 2.break break是结束循环的意思 举个栗子 #include<bits/stdc.h> using namespace std; int main(…...

    Windows 图形显示驱动开发-WDDM 2.4功能-GPU 半虚拟化(十一)

    注册表设置 GPU虚拟化标志 GpuVirtualizationFlags 注册表项用于设置半虚拟化 GPU 的行为。 密钥位于&#xff1a; DWORD HKLM\System\CurrentControlSet\Control\GraphicsDrivers\GpuVirtualizationFlags 定义了以下位&#xff1a; 位描述0x1 ​ 为所有硬件适配器强制设置…...

    Android在KSP中简单使用Room

    Android在KSP中简单使用Room 最近下载了最新版Studio&#xff0c;好多依赖和配置都需要升级&#xff0c;之前使用过room封装数据库工具类&#xff0c;最近在整理ksp相关&#xff0c;于是把room也升级了&#xff0c;简单记录一下升级过程&#xff0c;直接上代码。 1.添加KSP依…...

    Maven 构建配置文件详解

    Maven 构建配置文件详解 引言 Maven 是一个强大的项目管理和构建自动化工具,广泛应用于 Java 开发领域。在 Maven 项目中,配置文件扮演着至关重要的角色。本文将详细介绍 Maven 构建配置文件的相关知识,包括配置文件的作用、结构、配置方法等,帮助读者更好地理解和应用 M…...

    精确截图工具:基于 Tkinter 和 PyAutoGUI 的实现

    在日常工作中&#xff0c;截图是一个非常常见的需求。虽然 Windows 自带截图工具&#xff0c;但有时我们需要更精确的截图方式&#xff0c;比如选取特定区域、快速保存截图并进行预览。本篇博客将介绍一个使用 Python 结合 Tkinter 和 PyAutoGUI 开发的精确截图工具。 C:\pytho…...

    Linux练习——有关硬盘、联网、软件包的管理

    1、将你的虚拟机的网卡模式设置为nat模式&#xff0c;给虚拟机网卡配置三个主机位分别为100、200、168的ip地址 #使用nmtui打开文本图形界面配置网络 [rootrhcsa0306 ~]# nmtui #使用命令激活名为 ens160 的 NetworkManager 网络连接 [rootrhcsa0306 ~]# nmcli c up ens160 #通…...

    【C++】 —— 笔试刷题day_12

    一、删除公共字符 题目解析 题目给了两个字符串&#xff08;其中包含空格&#xff09;&#xff0c;让我们在第一个字符串中删除第二个字符串中的字符。 我们要输出删除后的字符串。 算法思路 这道题&#xff0c;如果直接按照题目中的要求去第一个字符串中删除字符&#xff0c…...

    家乡旅游景点小程序(源码+部署教程)

    运行环境 家乡旅游景点小程序运行环境如下&#xff1a; • 前端&#xff1a;小程序 • 后端&#xff1a;无 • IDE工具&#xff1a;微信开发者工具 • 技术栈&#xff1a;小程序 注意&#xff1a;此项目为纯静态项目&#xff0c;无后端 主要功能 家乡旅游景点微信小程序主…...

    SQL Server:当在删除数据库时因为存在触发器而无法删除

    当在删除数据库时因为存在触发器而无法删除&#xff0c;你可以通过禁用触发器来解决这个问题。下面为你介绍在 SQL Server 里禁用和启用触发器的方法。 禁用数据库中所有表的触发器 你可以使用系统视图 sys.triggers 来查询数据库里所有的触发器&#xff0c;然后生成禁用这些…...

    多人协同进行qt应用程序开发应该注意什么2?

    在多人协同开发Qt应用程序时&#xff0c;为了确保高效协作、代码一致性和项目可维护性&#xff0c;需要特别注意以下关键点&#xff1a; 1. 版本控制与协作流程 统一版本控制工具&#xff1a;使用Git并规范分支策略&#xff08;如Git Flow&#xff09;&#xff0c;通过.gitign…...

    js关于for of 与for in

    for…of for-of循环用于遍历可迭代对象&#xff0c;如数组、字符串、Map、Set等。它直接访问每个元素的值&#xff0c;而不是键名。 const arr [3,5,6,7,0] for(let item of arr){console.log(item); } // 3 // 5 // 6 // 7 // 0只有部署了Iterator接口的数据结构才能使用fo…...

    Python Excel

    一、Python读Excel——xlrd -*- coding: utf-8 -*- import xlrddef read_excel():打开文件workbook xlrd.open_workbook(rD:\demo1.xlsx)获取所有sheetprint(workbook.sheet_names()) 列表形式返回sheet1_name workbook.sheet_names()[0]根据sheet索引或者名称获取sheet内容…...

    前端全局编程和模块化编程

    1. 全局编程 <!DOCTYPE html> <html> <head><title>OpenLayers 示例</title><style>.map {width: 100%;height: 400px;}</style><script src"https://cdn.jsdelivr.net/npm/olv7.4.0/dist/ol.js"></script>&…...

    随机2级域名引导页HTML源码

    源码介绍 随机2级域名引导页HTML源码,每次点进去都随机一个域名前缀。 修改跳转域名在 350 行代码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行 效果预览 源码免费获取 随机2级域名引导页…...

    Latex的各种数学公式

    Latex的各种数学公式 简介公式1、 A 、 A ‾ \neg A\text{、}\overline{A} A、A2、 、 \text{、} 、3、 ⋅ 、 ∙ \cdot \text{、} \bullet ⋅、∙ 4、表格 简介 这里会随时更新我需要用到的数学公式&#xff0c;以csdn中写作格式为主&#xff0c;可能过时了&#xff0c;不适合…...

    稻壳模板下载器(Windows):免费获取WPS稻壳模板的利器

    稻壳模板下载器&#xff08;Win&#xff09; 稻壳模板下载器是一款功能强大的工具&#xff0c;能够帮助用户免费下载WPS稻壳儿中的各种模板&#xff0c;无需开通VIP会员。它支持多种模板类型&#xff0c;包括PPT、Word、Excel等&#xff0c;极大地提升了用户的办公效率。 依托…...

    BeanDefinition和Beanfactory实现一个简单的bean容器

    目录 什么是 Springbean 容器 设计思路 图解 参考文章 开源地址 BeanDefinition 类 BeanFactory 类 测试类 什么是 Springbean 容器 Spring 包含并管理应用对象的配置和生命周期&#xff0c;在这个意义上它是一种用于承载对象的容器&#xff0c;你可以配置你的每个 Bea…...

    Mybatis的resultMap标签介绍

    说明&#xff1a;在Mybatis中&#xff0c;resultMap 标签可以用于SQL查询后的封装数据&#xff0c;本文用两个场景介绍 resultMap 标签的使用。 搭建环境 先搭一个Demo&#xff0c;pom如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> &…...

    jarvisoj API调用 [JSON格式变XXE]

    http://web.jarvisoj.com:9882/ 题目要求&#xff1a;请设法获得目标机器 /home/ctf/flag.txt 中的flag值 抓包得到&#xff1a; POST /api/v1.0/try HTTP/1.1 Host: web.jarvisoj.com:9882 Content-Length: 36 Accept-Language: zh-CN,zh;q0.9 User-Agent: Mozilla/5.0 (W…...

    论坛系统的测试

    项目背景 论坛系统采用前后端分离的方式来实现&#xff0c;同时使用数据库 来处理相关的数据&#xff0c;同时将其部署到服务器上。前端主要有7个页面组成&#xff1a;登录页&#xff0c;列表页&#xff0c;论坛详情页&#xff0c;编辑页&#xff0c;个人信息页&#xff0c;我…...

    RK3588使用笔记:纯linux系统下基础功能配置(不定期更新)

    一、前言 用于记录使用RK3588这个平台在纯linux系统下的一些功能配置&#xff0c;RK3588只是一个芯片&#xff0c;linux只是一个系统&#xff0c;但是linux系统可以运行在无数的芯片上&#xff0c;也都大同小异&#xff0c;本编文章主要记录linux系统环境的一些常用的基础功能…...

    yum install 报错(CentOS换源):

    yum instally yum utils device mapper persistent-data lvm2 报错&#xff1a; 排查错误原因&#xff1a;centos7 系统停止维护了 解决方案&#xff1a;换源&#xff08;更换操作系统&#xff09; //1.备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-…...

    HTTP常见状态码分析

    当浏览者访问一个网页时&#xff0c;浏览者的浏览器会想网页所在的服务器发出请求&#xff0c;当浏览器接收并显示网页前&#xff0c;此网页所在的服务器会返回一个包含 HTTP 状态码的信息头&#xff08;server header&#xff09;用以响应浏览器的请求。 常见的状态码&#xf…...

    Python与Web 3.0支付系统:技术融合与未来展望

    Python与Web 3.0支付系统:技术融合与未来展望 随着区块链技术的不断发展,Web 3.0支付系统正逐步成为数字经济的重要组成部分。Python作为一种高效、易用的编程语言,在Web 3.0支付系统的开发中扮演着不可或缺的角色。本文将从技术背景、Python的应用、代码示例以及未来发展趋…...

    Linux命令-sed指令

    sed命令参数&#xff1a; 基本参数 -n&#xff1a;抑制默认输出&#xff0c;只显示匹配的行。 -e&#xff1a;指定 sed 脚本。 -i&#xff1a;直接修改文件内容。 -f&#xff1a;指定包含 sed 脚本的文件。 -r&#xff1a;启用扩展正则表达式。 常用操作 s&#xff1a;替换字符…...

    Unbantu24.04配置-软件安装

    Ubantu24.04配置—环境安装 ​ 最近在笔记本安装了双系统&#xff0c;这次在这里回顾一下&#xff0c;本章节主要是一些软件的注意点&#xff0c;大多数都是在网上有一定的教程的 1.搜狗输入法 1.1 删除其他框架 sudo apt purge ibus sudo apt remove fcitx5* sudo apt pur…...

    八股总结(Java)实时更新!

    八股总结&#xff08;java&#xff09; ArrayList和LinkedList有什么区别 ArrayList底层是动态数组&#xff0c;LinkedList底层是双向链表&#xff1b;前者利于随机访问&#xff0c;后者利于头尾插入&#xff1b;前者内存连续分配&#xff0c;后者通过指针连接多块不连续的内存…...

    NVIDIA TensorRT 10 [TAR]安装教程

    平台信息 操作系统&#xff1a;Ubuntu 20.04.6 LTSCPU架构&#xff1a;x86_64GPU&#xff1a;Tesla T4 x 2驱动信息&#xff1a; NVIDIA-SMI&#xff1a;535.104.05Driver Version: 535.104.05CUDA Version: 12.2 步骤 预备步骤 安装驱动和CUDA 假设已经成功安装好驱动&a…...

    深入探索 iOS 卡顿优化

    认识卡顿 一些概念 FPS&#xff1a;Frames Per Second&#xff0c;表示每秒渲染的帧数&#xff0c;通过用于衡量画面的流畅度&#xff0c;数值越高则表示画面越流畅。CPU&#xff1a;负责对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码…...

    【C/C++算法】从浅到深学习---分治算法之快排思想(图文兼备 + 源码详解)

    绪论&#xff1a;冲击蓝桥杯一起加油&#xff01;&#xff01; 每日激励&#xff1a;“不设限和自我肯定的心态&#xff1a;I can do all things。 — Stephen Curry” 绪论​&#xff1a;本章是针对快速排序进行的优化和再次理解快排思想&#xff0c;将会通过4道题目带你再次…...

    精通React JS中的API调用:示例指南

    精通React JS中的API调用:示例指南 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 精通React JS中的API调用:示例指南为什么在React JS中进行API调用?在React JS中制作API调用:…...

    浅谈Thread类及常见方法与线程的状态(多线程编程篇2)

    目录 前言 1.Thread类及常见方法 Thread类中常见的属性 1. getId() 2. getName() 3. getState() 4. getPriority() 5. isDaemon() 6. isAlive() 7. isInterrupted() 2.Thread类中常见的方法 Thread.interrupt() (中断线程) Thread.start()(启动线程) 1. 覆写 run…...

    算法刷题记录——LeetCode篇(1.2) [第11~20题](持续更新)

    更新时间&#xff1a;2025-03-29 LeetCode题解专栏&#xff1a;实战算法解题 (专栏)技术博客总目录&#xff1a;计算机技术系列目录页 优先整理热门100及面试150&#xff0c;不定期持续更新&#xff0c;欢迎关注&#xff01; 17. 电话号码的字母组合 给定一个仅包含数字 2-9…...

    基于HTML5和CSS3实现3D旋转相册效果

    基于HTML5和CSS3实现3D旋转相册效果 这里写目录标题 基于HTML5和CSS3实现3D旋转相册效果项目介绍技术栈核心功能实现原理1. HTML结构2. CSS样式设计2.1 基础样式设置2.2 容器样式2.3 图片样式 3. JavaScript实现4. 交互功能实现4.1 触摸和鼠标拖拽4.2 播放控制 项目亮点技术难点…...