ByteToMessageCodec

ByteToMessageCodec 是一个结合了 ByteToMessageDecoder 和 MessageToByteEncoder 的编解码器,可以实时地将字节流编码或解码为消息,反之亦然。

public abstract class ByteToMessageCodec<I> extends ChannelHandlerAdapter {private final TypeParameterMatcher outboundMsgMatcher;private final MessageToByteEncoder<I> encoder;private final ByteToMessageDecoder decoder = new ByteToMessageDecoder() {@Overridepublic void decode(ChannelHandlerContext ctx, Buffer in) throws Exception {ByteToMessageCodec.this.decode(ctx, in);}@Overrideprotected void decodeLast(ChannelHandlerContext ctx, Buffer in) throws Exception {ByteToMessageCodec.this.decodeLast(ctx, in);}};protected ByteToMessageCodec() {this((BufferAllocator) null);}protected ByteToMessageCodec(Class<? extends I> outboundMessageType) {this(outboundMessageType, null);}protected ByteToMessageCodec(BufferAllocator allocator) {outboundMsgMatcher = TypeParameterMatcher.find(this, ByteToMessageCodec.class, "I");encoder = new Encoder(allocator);}protected ByteToMessageCodec(Class<? extends I> outboundMessageType, BufferAllocator allocator) {outboundMsgMatcher = TypeParameterMatcher.get(outboundMessageType);encoder = new Encoder(allocator);}@Overridepublic final boolean isSharable() {return false;}public boolean acceptOutboundMessage(Object msg) throws Exception {return outboundMsgMatcher.match(msg);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {decoder.channelRead(ctx, msg);}@Overridepublic Future<Void> write(ChannelHandlerContext ctx, Object msg) {return encoder.write(ctx, msg);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {decoder.channelReadComplete(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {decoder.channelInactive(ctx);}@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {try {decoder.handlerAdded(ctx);} finally {encoder.handlerAdded(ctx);}}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {try {decoder.handlerRemoved(ctx);} finally {encoder.handlerRemoved(ctx);}}protected abstract void encode(ChannelHandlerContext ctx, I msg, Buffer out) throws Exception;protected abstract void decode(ChannelHandlerContext ctx, Buffer in) throws Exception;protected void decodeLast(ChannelHandlerContext ctx, Buffer in) throws Exception {if (in.readableBytes() > 0) {decode(ctx, in);}}private final class Encoder extends MessageToByteEncoder<I> {private final BufferAllocator allocator;Encoder(BufferAllocator allocator) {this.allocator = allocator;}@Overridepublic boolean acceptOutboundMessage(Object msg) throws Exception {return ByteToMessageCodec.this.acceptOutboundMessage(msg);}@Overrideprotected Buffer allocateBuffer(ChannelHandlerContext ctx, I msg) throws Exception {BufferAllocator alloc = allocator != null? allocator : ctx.bufferAllocator();return alloc.allocate(256);}@Overrideprotected void encode(ChannelHandlerContext ctx, I msg, Buffer out) throws Exception {ByteToMessageCodec.this.encode(ctx, msg, out);}}
}

MessageToByteEncoder

MessageToByteEncoder 是一个用于将消息编码为字节流的抽象类。它继承自 ChannelHandlerAdapter,并通过两个抽象方法来实现编码功能:allocateBuffer 用于分配一个 Buffer,encode 用于将消息编码到 Buffer 中。它通过 acceptOutboundMessage 方法决定是否处理给定的消息类型,并在 write 方法中执行编码操作。

public abstract class MessageToByteEncoder<I> extends ChannelHandlerAdapter {private final TypeParameterMatcher matcher;protected MessageToByteEncoder() {matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");}protected MessageToByteEncoder(Class<? extends I> outboundMessageType) {matcher = TypeParameterMatcher.get(requireNonNull(outboundMessageType, "outboundMessageType"));}public boolean acceptOutboundMessage(Object msg) throws Exception {return matcher.match(msg);}@Overridepublic Future<Void> write(ChannelHandlerContext ctx, Object msg) {Buffer buf = null;try {if (acceptOutboundMessage(msg)) {@SuppressWarnings("unchecked")I cast = (I) msg;buf = allocateBuffer(ctx, cast);try (AutoCloseable ignore = autoClosing(cast)) {encode(ctx, cast, buf);}if (buf.readableBytes() > 0) {Future<Void> f = ctx.write(buf);buf = null;return f;}return ctx.write(ctx.bufferAllocator().allocate(0));}return ctx.write(msg);} catch (EncoderException e) {return ctx.newFailedFuture(e);} catch (Throwable e) {return ctx.newFailedFuture(new EncoderException(e));} finally {if (buf != null) {buf.close();}}}protected abstract Buffer allocateBuffer(ChannelHandlerContext ctx, I msg) throws Exception;protected abstract void encode(ChannelHandlerContext ctx, I msg, Buffer out) throws Exception;
}

ByteToMessageDecoder

ByteToMessageDecoder 是 Netty 5 中用于处理 TCP 粘包拆包的解码器基础类。它通过维护一个累积缓冲区 cumulation,将接收到的 Buffer 连续拼接、缓存,并在适当时机调用 decode(…) 方法将字节流转为高层消息对象

public abstract class ByteToMessageDecoder extends ChannelHandlerAdapter {public static final Cumulator MERGE_CUMULATOR = new MergeCumulator();public static final Cumulator COMPOSITE_CUMULATOR = new CompositeBufferCumulator();private final int discardAfterReads = 16;private final Cumulator cumulator;// 累积的字节缓冲区,保存之前未解码完的字节private Buffer cumulation;// 是否每次只解码一条消息,默认关闭以提升性能,开启后适合协议升级场景private boolean singleDecode;// 标记是否是第一次解码调用private boolean first;// 标记本次 decode() 是否产出并传递了消息,决定是否需要继续读。private boolean firedChannelRead;// 标记当前的 channelRead 是 decoder 主动触发的,防止重复触发 read()。private boolean selfFiredChannelRead;// 统计读操作次数private int numReads;// 包装的上下文对象private ByteToMessageDecoderContext context;protected ByteToMessageDecoder() {this(MERGE_CUMULATOR);}protected ByteToMessageDecoder(Cumulator cumulator) {this.cumulator = requireNonNull(cumulator, "cumulator");}@Overridepublic final boolean isSharable() {// Can't be sharable as we keep state.return false;}public void setSingleDecode(boolean singleDecode) {this.singleDecode = singleDecode;}public boolean isSingleDecode() {return singleDecode;}protected int actualReadableBytes() {return internalBuffer().readableBytes();}protected Buffer internalBuffer() {return cumulation;}@Overridepublic final void handlerAdded(ChannelHandlerContext ctx) throws Exception {context = new ByteToMessageDecoderContext(ctx);handlerAdded0(context);}protected void handlerAdded0(ChannelHandlerContext ctx) throws Exception {}@Overridepublic final void handlerRemoved(ChannelHandlerContext ctx) throws Exception {Buffer buf = cumulation;if (buf != null) {cumulation = null;numReads = 0;int readable = buf.readableBytes();if (readable > 0) {ctx.fireChannelRead(buf);ctx.fireChannelReadComplete();} else {buf.close();}}handlerRemoved0(context);}protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { }// 1. TCP 是流式协议,发送方即便发送了一整条消息,接收方可能会分多次读取数据// 2. 每次读取都触发channelRead,读取到的msg是Buffer类型,内容可能不完整,需要累积合并到 cumulation// 3. 每次读取都会触发callDecode,去尝试根据现有的数据,进行解码,如果成功则向调用链传递结果,否则啥也不干,等待下次 channelRead@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {if (msg instanceof Buffer) {selfFiredChannelRead = true;try {Buffer data = (Buffer) msg;first = cumulation == null;if (first) {cumulation = data;} else {cumulation = cumulator.cumulate(ctx.bufferAllocator(), cumulation, data);}assert context.delegatingCtx() == ctx || ctx == context;// 尝试解码callDecode(context, cumulation);} catch (DecoderException e) {throw e;} catch (Exception e) {throw new DecoderException(e);} finally {// 如果数据已读尽且无剩余,释放 bufferif (cumulation != null && cumulation.readableBytes() == 0) {numReads = 0;if (cumulation.isAccessible()) {cumulation.close();}cumulation = null;} else if (++numReads >= discardAfterReads) {// 如果累计读取次数到达阈值,主动丢弃已消费字节,防止 cumulation 越来越大numReads = 0;discardSomeReadBytes();}// 跟踪本次读数据是否至少成功解码过一次, 向下游传播了消息firedChannelRead |= context.fireChannelReadCallCount() > 0;// 状态清理context.reset();}} else {ctx.fireChannelRead(msg);}}// 在 channelRead 之后,执行 channelReadComplete@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {// 重置读取次数,清理 cumulation 的无效字节。numReads = 0;discardSomeReadBytes();// 解码还未成功过,自动读取关闭,在 channelRead完成后,执行本方法时,手动触发下一次读取if (selfFiredChannelRead && !firedChannelRead && !ctx.channel().getOption(ChannelOption.AUTO_READ)) {ctx.read(); }// 重置状态标志位并通知下游 handler。firedChannelRead = false;selfFiredChannelRead = false;ctx.fireChannelReadComplete();}protected final void discardSomeReadBytes() {// 丢弃 cumulation 中已消费的字节。只在存在历史累积(!first)时执行if (cumulation != null && !first) {cumulator.discardSomeReadBytes(cumulation);}}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {assert context.delegatingCtx() == ctx || ctx == context;channelInputClosed(context, true);}@Overridepublic void channelShutdown(ChannelHandlerContext ctx, ChannelShutdownDirection direction) throws Exception {ctx.fireChannelShutdown(direction);if (direction == ChannelShutdownDirection.Inbound) {assert context.delegatingCtx() == ctx || ctx == context;channelInputClosed(context, false);}}// Channel 被关闭时,尝试从 cumulation 中解码剩余数据,释放资源,并向下传递 fire* 事件private void channelInputClosed(ByteToMessageDecoderContext ctx, boolean callChannelInactive) {try {channelInputClosed(ctx);} catch (DecoderException e) {throw e;} catch (Exception e) {throw new DecoderException(e);} finally {if (cumulation != null) {cumulation.close();cumulation = null;}if (ctx.fireChannelReadCallCount() > 0) {ctx.reset();ctx.fireChannelReadComplete();}if (callChannelInactive) {ctx.fireChannelInactive();}}}void channelInputClosed(ByteToMessageDecoderContext ctx) throws Exception {if (cumulation != null) {callDecode(ctx, cumulation);if (!ctx.isRemoved()) {if (cumulation == null) {try (Buffer buffer = ctx.bufferAllocator().allocate(0)) {decodeLast(ctx, buffer);}} else {decodeLast(ctx, cumulation);}}} else {try (Buffer buffer = ctx.bufferAllocator().allocate(0)) {decodeLast(ctx, buffer);}}}void callDecode(ByteToMessageDecoderContext ctx, Buffer in) {try {// 当仍有未解码的数据 (in.readableBytes() > 0) 且当前 Handler 还在 pipeline 中(未被移除),就继续调用 decode() 试图解码更多消息。while (in.readableBytes() > 0 && !ctx.isRemoved()) {int oldInputLength = in.readableBytes();int numReadCalled = ctx.fireChannelReadCallCount();decodeRemovalReentryProtection(ctx, in);if (ctx.isRemoved()) {break;}if (numReadCalled == ctx.fireChannelReadCallCount()) {if (oldInputLength == in.readableBytes()) {break;} else {continue;}}if (oldInputLength == in.readableBytes()) {throw new DecoderException(StringUtil.simpleClassName(getClass()) +".decode() did not read anything but decoded a message.");}if (isSingleDecode()) {break;}}} catch (DecoderException e) {throw e;} catch (Exception cause) {throw new DecoderException(cause);}}protected void decodeLast(ChannelHandlerContext ctx, Buffer in) throws Exception {if (in.readableBytes() > 0) {decodeRemovalReentryProtection(ctx, in);}}final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, Buffer in) throws Exception {decode(ctx, in);}// 建议结合 FixedLengthFrameDecoder阅读,理解decode方法做了什么protected abstract void decode(ChannelHandlerContext ctx, Buffer in) throws Exception;// ...
}

ByteToMessageDecoderContext

ByteToMessageDecoderContext 是 Netty 为解码器设计的包装上下文,用于统计 fireChannelRead 次数,以精确控制解码行为和数据流转。

static final class ByteToMessageDecoderContext extends DelegatingChannelHandlerContext {private int fireChannelReadCalled;private ByteToMessageDecoderContext(ChannelHandlerContext ctx) {super(ctx);}void reset() {fireChannelReadCalled = 0;}int fireChannelReadCallCount() {return fireChannelReadCalled;}@Overridepublic ChannelHandlerContext fireChannelRead(Object msg) {fireChannelReadCalled ++;super.fireChannelRead(msg);return this;}
}

Cumulator

Cumulator 接口用于处理和累积多个 Buffer 数据。它有两个主要方法,分别负责将多个 Buffer 合并成一个更大的 Buffer,并管理已经处理过的数据。

public interface Cumulator {// BufferAllocator alloc:缓冲区分配器,用于分配新的 Buffer// Buffer cumulation:当前的累积数据缓冲区// Buffer in:新的输入数据缓冲区// 将多个 Buffer 合并成一个新的 Buffer,即将当前 Buffer 中的可读数据与新的数据合并Buffer cumulate(BufferAllocator alloc, Buffer cumulation, Buffer in);// 丢弃缓冲区中已经读取的数据,返回一个新的缓冲区,去除了之前已处理过的部分Buffer discardSomeReadBytes(Buffer cumulation);
}
CompositeBufferCumulator

CompositeBufferCumulator 适用于大量 Buffer 分段输入时,通过复合缓冲区高效拼接,避免数据拷贝和缓冲区重分配。

private static final class CompositeBufferCumulator implements Cumulator {@Overridepublic Buffer cumulate(BufferAllocator alloc, Buffer cumulation, Buffer in) {// 如果累加缓冲区 cumulation 没有可读字节了,直接释放并返回输入缓冲区 inif (cumulation.readableBytes() == 0) {cumulation.close();return in;}try (in) {// 输入缓冲区 in 没有可读字节,保留原来的 cumulation。if (in.readableBytes() == 0) {return cumulation;}// 如果累加缓冲区是只读的,复制出一个可写副本if (cumulation.readOnly()) {Buffer tmp = cumulation.copy();cumulation.close();cumulation = tmp;}// 如果当前累加区已经是 CompositeBuffer,直接扩展进去if (CompositeBuffer.isComposite(cumulation)) {CompositeBuffer composite = (CompositeBuffer) cumulation;composite.extendWith(prepareInForCompose(in));return composite;}return alloc.compose(Arrays.asList(cumulation.send(), prepareInForCompose(in)));}}// 确保 in 是只读安全的后,调用 send() 移交所有权用于合并。private static Send<Buffer> prepareInForCompose(Buffer in) {return in.readOnly() ? in.copy().send() : in.send();}// 通过 readSplit(0) 剪掉已经读过的部分,释放空间。相比 compact() 更适合复合缓冲区(CompositeBuffer)@Overridepublic Buffer discardSomeReadBytes(Buffer cumulation) {cumulation.readSplit(0).close();return cumulation;}@Overridepublic String toString() {return "CompositeBufferCumulator";}
}
MergeCumulator

MergeCumulator 更适合频繁接收小块数据的场景,追求高访问性能和代码简单;但当数据量变大时,可能因为频繁扩容带来复制开销。

    private static final class MergeCumulator implements Cumulator {@Overridepublic Buffer cumulate(BufferAllocator alloc, Buffer cumulation, Buffer in) {// 如果当前累积的 Buffer 为空if (cumulation.readableBytes() == 0) {cumulation.close();return in;}try (in) {final int required = in.readableBytes();// 如果当前的累积 Buffer 没有足够的空间,或者它是只读,就扩展大小。if (required > cumulation.writableBytes() || cumulation.readOnly()) {return expandCumulationAndWrite(alloc, cumulation, in);}cumulation.writeBytes(in);return cumulation;}}// 如果 cumulation 中的已读字节数超过可写字节数(readerOffset() > writableBytes()),则调用 compact() 方法来压缩缓冲区,移除已读的数据@Overridepublic Buffer discardSomeReadBytes(Buffer cumulation) {if (cumulation.readerOffset() > cumulation.writableBytes()) {cumulation.compact();}return cumulation;}private static Buffer expandCumulationAndWrite(BufferAllocator alloc, Buffer oldCumulation, Buffer in) {// 1. 计算新的 Buffer 大小:计算新的 Buffer 大小,确保它能够容纳当前的 cumulation 和输入的 Buffer。新的大小是当前已读字节和输入字节总和的下一个最接近的 2 的幂次方。final int newSize = safeFindNextPositivePowerOfTwo(oldCumulation.readableBytes() + in.readableBytes());// 创建新的 Buffer:根据是否是只读的 Buffer 来决定如何创建新的 Buffer。如果是只读的,会重新分配内存;否则,直接扩展当前的 Buffer。Buffer newCumulation = oldCumulation.readOnly() ? alloc.allocate(newSize) :oldCumulation.ensureWritable(newSize);// 3. 将旧数据和新数据写入新 Buffer:如果创建了新的 Buffer,将旧的 cumulation 和输入的 Buffer 都写入新的 Buffer。try {if (newCumulation != oldCumulation) {newCumulation.writeBytes(oldCumulation);}newCumulation.writeBytes(in);return newCumulation;} finally {// 4. 关闭旧的 Buffer:如果创建了新的 Buffer,则关闭原先的 cumulation,释放内存。if (newCumulation != oldCumulation) {oldCumulation.close();}}}@Overridepublic String toString() {return "MergeCumulator";}}
}

FixedLengthFrameDecoder

在此引入这个类,只是因为这个类最简单,方便大家理解 解码器的工作流程。

  1. FixedLengthFrameDecoder:按照固定的字节长度切割每一帧,适用于每条消息长度一致的协议(如定长二进制协议)。
  2. LengthFieldBasedFrameDecoder:根据消息中指定位置的“长度字段”值来动态切割完整帧,适用于二进制协议。
  3. LineBasedFrameDecoder:以 \n 或 \r\n 为分隔符,将每行作为一帧,适用于基于文本的行协议。
  4. DelimiterBasedFrameDecoder:使用自定义的分隔符(如 $_、#END#)拆分帧,适用于定界符结束的文本协议。
public class FixedLengthFrameDecoder extends ByteToMessageDecoder {private final int frameLength;public FixedLengthFrameDecoder(int frameLength) {checkPositive(frameLength, "frameLength");this.frameLength = frameLength;}public FixedLengthFrameDecoder(int frameLength, Cumulator cumulator) {super(cumulator);checkPositive(frameLength, "frameLength");this.frameLength = frameLength;}// ByteToMessageDecoder(channelRead[首次读] -> callDecode[循环读取数据]// ->// decodeRemovalReentryProtection) // -> // FixedLengthFrameDecoder.decode@Overrideprotected final void decode(ChannelHandlerContext ctx, Buffer in) throws Exception {Object decoded = decode0(ctx, in);if (decoded != null) {// 只要解码成功,就向下传递结果// ByteToMessageDecoderContext.fireChannelReadctx.fireChannelRead(decoded);}}protected Object decode0(@SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, Buffer in) throws Exception {if (in.readableBytes() < frameLength) {return null;} else {return in.readSplit(frameLength);}}
}

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

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

相关文章

Ubuntu20.04安装mujoco210, mujoco-py时的报错处理

参考 Ubantu 20.04 安装 Mujoco210、mujoco-py、gym及报错解决 安装 mujoco210 创建 .mujoco 文件夹 mkdir ~/.mujoco亲测必须是 .mujoco 文件夹&#xff0c;不然会报错&#xff01; 下载 mujoco210-linux-x86_64.tar.gz 并解压到 .mujoco 文件夹 mojoco下载地址 测试 mojo…

全志T507 音频ALSA核心层注册流程分析

一.ALSA核心层注册流程分析 驱动目录&#xff1a;kernel-4.9/sound/core/sound.c struct file_operations snd_fops {.owner THIS_MODULE,.open snd_open, (inode, file)---->struct snd_minor *mptr snd_minors[minor];---->file->f_op fops_get(mptr->f_ops…

评论区实现 前端Vue

根据后端部分定义评论区功能实现 golang后端部分-CSDN博客&#xff0c;重点需要实现三个部分&#xff0c;1.当前用户发起新根评论请求&#xff1b;2.评论区展示部分&#xff1b;3.某一根评论的子评论展示以及回复组件显示。 整体流程解释 数据从后端接收&#xff0c;整体在in…

差分定位技术:原理、分类与应用场景

文章目录 简介基本概念位置差分伪距差分载波相位 差分定位技术精密单点定位&#xff08;PPP&#xff09;差分全球定位系统&#xff08;DGPS&#xff09;实时动态定位&#xff08;RTK&#xff09; 应用场景总结 简介 差分定位&#xff08;Differential Positioning&#xff09;是…

tomcat的tar包转换成rpm包的保姆级教程

环境说明 &#xff1a;centos 71. 安装打包工具&#xff1a;yum install -y rpm-build rpmdevtools2. 创建 RPM 打包环境&#xff1a;rpmdev-setuptree​输入之后是下面的结果~/rpmbuild/ ├── BUILD ├── RPMS ├── SOURCES ├── SPECS └── SRPMS​准备 Tomcat 源码…

【牛客算法】小美的数组删除

文章目录 一、题目介绍二、解题思路三、解题算法实现四、算法分析4.1 代码逻辑4.2 逆向遍历求MEX的设计精妙之处4.2.1 逆向遍历:解决MEX更新的连续性4.2.2 利用MEX的单调性4.2.3 空间复用与状态压缩4.2.4 与问题特性的完美契合4.2.5 总结:为什么说这个设计“妙”?五、算法复…

MyBatisPlus-01-环境初始化及简单应用

文章目录【README】【1】springboot集成mybatis-plus配置【1.1】目录结构【相关说明】【1.2】代码示例【pom.xml】【application.properties】【MybatisPlusNoteController】【UserAppService】【UserMapper】【UserPO】【建表语句】【2】演示【README】 本文代码参见&#xf…

Web爬虫编程语言选择指南

刚学爬虫的小伙伴常常为选择那种语言来写爬虫而烦恼&#xff0c;今天我将总结几种语言的优劣势&#xff0c;然后选择适合编写 Web爬虫 的编程语言。这就需要我们考虑开发效率、生态库支持、并发性能等因素。以下是主流选择及特点跟着一起看看吧&#xff1a; 1. Python&#xff…

学习日志06 python

加油&#xff0c;今天的任务是学习面向对象编程&#xff0c;设计一个简单的宠物管理系统&#xff08;宠物类、猫 / 狗子类&#xff09;&#xff0c;先做5道题目开启学习状态吧&#xff01;1 setdefault()在 Python 中&#xff0c;setdefault() 是字典&#xff08;dict&#xff…

基于Java+springboot 的车险理赔信息管理系统

源码、数据库、包调试源码编号&#xff1a;S595源码名称&#xff1a;基于springboot 的车险理赔信息管理系统用户类型&#xff1a;多角色&#xff0c;用户、事故调查员、管理员数据库表数量&#xff1a;14 张表主要技术&#xff1a;Java、Vue、ElementUl 、SpringBoot、Maven运…

MyDockFinder 绿色便携版 | 一键仿Mac桌面,非常简单

如果你既不想升级到Win11&#xff0c;又想体验Mac桌面的高级感&#xff0c;那么MyDockFinder将是你的最佳选择。这是一款专为Windows系统设计的桌面美化工具&#xff0c;能够将你的桌面转变成MacOS的风格。它提供了类似Dock栏和Finder的功能&#xff0c;让你在不更换操作系统的…

Babylon.js 材质克隆与纹理共享:你可能遇到的问题及解决方案

在 Babylon.js 中&#xff0c;材质&#xff08;Material&#xff09;和纹理&#xff08;Texture&#xff09;的克隆行为可能会影响渲染性能和内存管理&#xff0c;尤其是在多个材质共享同一纹理的情况下。本文将探讨&#xff1a;PBRMetallicRoughnessMaterial 的克隆机制&#…

信息素养复赛模拟1和模拟2的编程题标程

信息素养复赛模拟 11&#xff1a;楼层编号 #include<bits/stdc.h> using namespace std; int main(){int n, t;cin >> n >> t;int res 0;for(int i 1; i < n; i ){int x i;bool ok true;while(x){if(x % 10 t){ok false;}x / 10;}res ok;} cout &l…

Hadoop高可用集群搭建

Hadoop高可用(HA)集群是企业级大数据平台的核心基础设施&#xff0c;通过多主节点冗余和自动故障转移机制&#xff0c;确保系统在单点故障时仍能正常运行。本文将详细介绍如何基于CentOS 7搭建Hadoop 3.X高可用集群&#xff0c;涵盖环境准备、组件配置、集群启动及管理的全流程…

Next.js 实战笔记 1.0:架构重构与 App Router 核心机制详解

Next.js 实战笔记 1.0&#xff1a;架构重构与 App Router 核心机制详解 上一次写 Next 相关的东西都是 3 年前的事情了&#xff0c;这 3 年里 Next 也经历了 2-3 次的大版本变化。当时写的时候 Next 是 12 还是 13 的&#xff0c;现在已经是 15 了&#xff0c;从 build 到实现…

Pillow 安装使用教程

一、Pillow 简介 Pillow 是 Python 图像处理库 PIL&#xff08;Python Imaging Library&#xff09;的友好分支&#xff0c;是图像处理的事实标准。它支持打开、编辑、转换、保存多种图像格式&#xff0c;常用于图像批量处理、验证码识别、缩略图生成等应用场景。 二、安装 Pi…

SQL Server从入门到项目实践(超值版)读书笔记 20

9.4 数据的嵌套查询所谓嵌套查询&#xff0c;就是在一个查询语句中&#xff0c;嵌套进另一个查询语句&#xff0c;即&#xff0c;查询语句中可以使用另一个查询语句中得到的查询结果&#xff0c;子查询可以基于一张表或者多张表。子查询中常用的操作符有ANY、SOME、ALL、IN、EX…

【MySQL\Oracle\PostgreSQL】迁移到openGauss数据出现的问题解决方案

【MySQL\Oracle\PostgreSQL】迁移到openGauss数据出现的问题解决方案 问题1&#xff1a;序列值不自动刷新问题 下面SQL只针对单库操作以及每个序列只绑定一张表的情况 -- 自动生成的序列&#xff0c;设置序列值 with sequences as (select *from (select table_schema,table_…

【Maven】Maven命令大全手册:28个核心指令使用场景

Maven命令大全手册&#xff1a;28个核心指令使用场景 Maven命令大全手册&#xff1a;28个核心指令深度解析一、构建生命周期核心命令1. mvn clean2. mvn compile3. mvn test4. mvn package5. mvn install6. mvn deploy二、依赖管理命令7. mvn dependency:tree8. mvn dependency…

大语言模型(LLM)按架构分类

大语言模型&#xff08;LLM&#xff09;按架构分类的深度解析 1. 仅编码器架构&#xff08;Encoder-Only&#xff09; 原理 双向注意力机制&#xff1a;通过Transformer编码器同时捕捉上下文所有位置的依赖关系# 伪代码示例&#xff1a;BERT的MLM任务 masked_input "Th…