[netty5: ByteToMessageCodec MessageToByteEncoder ByteToMessageDecoder]-源碼分析

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…