Kafka 如何優雅實現 Varint 和 ZigZag 編碼

ByteUtils?是 Kafka 中一個非常基礎且核心的工具類。從包名?common.utils?就可以看出,它被廣泛用于 Kafka 的各個模塊中。它的主要職責是提供一套高效、底層的靜態方法,用于在字節緩沖區 (ByteBuffer)、字節數組 (byte[]) 以及輸入/輸出流 (InputStream/OutputStream) 中讀寫 Java 的基本數據類型。


ZigZag 編解碼過程的數學原理詳解

康托爾對角線映射。

可以找到一種方式,任何一個有理數都可以在有限步驟被枚舉到

可以見?哥德爾定理?的討論

ZigZag 編碼是一種巧妙的算法,它能將有符號整數(正數、負數、零)映射到無符號整數數軸上,其核心優勢在于能將絕對值小的數(無論正負)都映射為小的無符號整數。這使得它與 Varint 編碼結合使用時,能極大地壓縮數據體積。

其編解碼過程可以分為對非負數和負數兩種情況進行討論。


編碼過程 (Signed -> Unsigned)

編碼操作由公式 (n << 1) ^ (n >> 63)(以64位 long 為例)實現,我們可以將其拆解為兩種情況:

對于非負數 (x >= 0):

  • ??編碼公式??: x -> 2*x
  • ??推導??:
    當 x 為非負數時,x >> 63 的結果是 0。
    因此編碼公式簡化為 (x << 1) ^ 0,即 x * 2
  • ??示例??:
    0 -> 0, 1 -> 2, 2 -> 4, ...

對于負數 (x < 0):

  • ??編碼公式??: x -> -2*x - 1
  • ??推導??:
    當 x 為負數時,x >> 63 的結果是 -1(二進制全為1)。
    編碼公式變為 (x << 1) ^ -1
    ^ -1 在二進制中等價于按位取反 (~)。
    因此,編碼結果為 ~(x * 2)
    根據二進制補碼的性質,~a = -a - 1,所以 ~(x * 2) 等于 - (x * 2) - 1,即 -2x - 1
  • ??示例??:
    -1 -> 1, -2 -> 3, -3 -> 5, ...

??效果??:
通過這種方式,正數被映射到偶數,負數被映射到奇數,實現了在無符號數軸上的“之”字形(ZigZag)交錯排列。


解碼過程 (Unsigned -> Signed)

解碼操作由公式 (y >>> 1) ^ -(y & 1) 實現,其中 y 是編碼后的無符號數。

(1) y >>> 1(無符號右移一位):

  • ??數學意義??:
    等價于 y / 2(向下取整)。
  • ??對非負數編碼結果 (y 為偶數)??:
    y/2 直接得到原始值 x,解碼完成。
  • ??對負數編碼結果 (y 為奇數)??:
    已知 y = -2x - 1,此時 y/2 = (-2x - 1) / 2 = -x - 1(向下取整)。

(2) -(y & 1)(判斷奇偶并生成掩碼):

  • ??作用??:
    y & 1 用于判斷 y 的奇偶性:
    • 若 y 為偶數,結果為 0;
    • 若 y 為奇數,結果為 1。
  • ??掩碼生成??:
    -(y & 1) 生成掩碼:
    • y 為偶數時,掩碼為 0;
    • y 為奇數時,掩碼為 -1(二進制全為1)。

(3) ^(異或操作):

  • ??當 y 為偶數(來自非負數)??:
    解碼公式為 (y/2) ^ 0,結果即 y/2(原始值 x)。
  • ??當 y 為奇數(來自負數)??:
    解碼公式為 (y/2) ^ -1
    已知此時 y/2 = -x - 1,因此:
    (-x - 1) ^ -1
    ^ -1 等價于按位取反 (~),故結果為 ~(-x - 1)
    根據補碼性質 ~a = -a - 1,推導如下:
    ~(-x - 1) = -(-x - 1) - 1 = (x + 1) - 1 = x
    最終還原為原始負數 x。

??總結??:
通過這一系列精巧的位運算,解碼過程成功將無符號數還原為原始有符號數。

可變長度整數(Varints)和長整數(Varlongs)

這是?ByteUtils?中非常重要的一部分,也是 Kafka 實現高效數據壓縮的關鍵技術之一。Varint 是一種使用一個或多個字節序列化整數的方法,數值越小的整數(絕對值)占用的字節數越少。這對于存儲大量小整數(如長度、數量等)的場景能有效節省空間。

Kafka 的 Varint 實現參考了 Google Protocol Buffers 的編碼方案。

無符號 Varint (Unsigned Varint)

這是 Varint 的基礎。它將一個 32 位整數編碼為 1 到 5 個字節。每個字節的最高位(MSB)是標志位,1?表示后面還有字節,0?表示這是最后一個字節。剩下的 7 位用于存儲數據。

  • 讀取 (readUnsignedVarint):
     
    // ... existing code ...
    public static int readUnsignedVarint(ByteBuffer buffer) {byte tmp = buffer.get();if (tmp >= 0) {return tmp;} else {int result = tmp & 127;if ((tmp = buffer.get()) >= 0) {result |= tmp << 7;} else {result |= (tmp & 127) << 7;if ((tmp = buffer.get()) >= 0) {result |= tmp << 14;} else {result |= (tmp & 127) << 14;if ((tmp = buffer.get()) >= 0) {result |= tmp << 21;} else {result |= (tmp & 127) << 21;result |= (tmp = buffer.get()) << 28;if (tmp < 0) {throw illegalVarintException(result);}}}}return result;}
    }
    // ... existing code ...
    
    • 代碼分析: 這段代碼通過一系列的?if-else?結構展開了循環,這是一種為了性能的優化(循環展開)。
    • 它逐字節讀取,檢查最高位(通過?tmp >= 0?判斷,如果為正數,說明最高位是0)。
    • 如果最高位是1,就取其低7位 (tmp & 127),并將其拼接到結果?result?的高位上,然后繼續讀取下一個字節。
    • 如果讀取超過5個字節仍然沒有結束,會拋出異常。

有符號 Varint (Signed Varint?- ZigZag 編碼)

直接用 Varint 編碼負數效率很低(例如 -1 會被編碼成 5 個字節)。為了高效地編碼有符號數,特別是那些絕對值較小的負數,Kafka 使用了 ZigZag 編碼。它通過一種位操作,將有符號數映射到無符號數上,使得絕對值小的數(無論正負)都映射為小的無符號數。

  • 映射規則:
    • (n << 1) ^ (n >> 31)?for signed?n

// ... existing code ...
public static int readVarint(ByteBuffer buffer) {int value = readUnsignedVarint(buffer);return (value >>> 1) ^ -(value & 1);
}
// ... existing code ...

這個方法的核心作用是解碼一個經過?ZigZag(對角線)編碼?和?Varint 編碼?的整數。整個過程分為兩步:

  1. 從?ByteBuffer?中讀取一個經過 Varint 編碼的無符號整數。
  2. 對這個無符號整數進行 ZigZag 解碼,將其還原為原始的有符號整數。

1.?int value = readUnsignedVarint(buffer);

這一步是 Varint 解碼。它首先調用?readUnsignedVarint?方法,該方法會從字節流中讀取 1 到 5 個字節,并將它們解析成一個32位的無符號整數。這個解析出來的?value?并不是最終結果,而是經過 ZigZag 編碼后的中間值。

2.?return (value >>> 1) ^ -(value & 1);

這是整個方法最關鍵的部分,即?ZigZag(對角線)解碼。這一行代碼非常精妙,它將上一步得到的無符號整數?value?還原回它所代表的原始有符號整數。

為什么需要 ZigZag 編碼?

Varint 編碼對于小的正整數效率很高(例如,0-127 只需要1個字節)。但對于負數,其二進制補碼表示通常是一個很大的正數(例如,-1 的補碼是?0xFFFFFFFF),如果直接用 Varint 編碼,會占用最多的5個字節,完全失去了 Varint 的優勢。

ZigZag 編碼解決了這個問題。它通過一種位運算,將有符號整數“之”字形地映射到無符號整數上,從而保證絕對值小的數(無論正負)都會被映射成小的無符號數。

映射關系(對角線/ZigZag 編碼)

原始有符號值 (Original Signed)編碼后無符號值 (Encoded Unsigned)
00
-11
12
-23
24
......
2,147,483,6474,294,967,294
-2,147,483,6484,294,967,295

解碼公式?(value >>> 1) ^ -(value & 1)?的剖析

讓我們通過兩個例子來理解這個解碼過程:

  • 示例 1: 解碼?-1

    1. 從映射表可知,-1?編碼后的值為?1。所以?value = 1
    2. value & 1?=>?1 & 1?=>?1。 (取最低位,用于判斷原始值的符號)
    3. -(value & 1)?=>?-1。在二進制補碼中,-1?是?...11111111
    4. value >>> 1?=>?1 >>> 1?=>?0。 (無符號右移一位,獲取數值部分)
    5. 0 ^ -1?=>?000...000 ^ 111...111?=>?111...111。結果是?-1解碼正確
  • 示例 2: 解碼?2

    1. 從映射表可知,2?編碼后的值為?4。所以?value = 4?(二進制?...00000100)。
    2. value & 1?=>?4 & 1?=>?0
    3. -(value & 1)?=>?-0?=>?0
    4. value >>> 1?=>?4 >>> 1?=>?2
    5. 2 ^ 0?=>?2解碼正確

readVarlong

readVarlong?和?writeVarlong?是?Varint?的 64 位版本,原理完全相同,只是最多可以占用 10 個字節,同樣也使用了 ZigZag 編碼來處理有符號長整型。

// ... existing code ...public static long readVarlong(ByteBuffer buffer)  {long raw =  readUnsignedVarlong(buffer);return (raw >>> 1) ^ -(raw & 1);}// visible for testingstatic long readUnsignedVarlong(ByteBuffer buffer)  {long value = 0L;int i = 0;long b;while (((b = buffer.get()) & 0x80) != 0) {value |= (b & 0x7f) << i;i += 7;if (i > 63)throw illegalVarlongException(value);}value |= b << i;return value;}
// ... existing code ...
  • 代碼分析:?readUnsignedVarlong?使用了?while?循環,邏輯更清晰。它不斷讀取字節,只要字節的最高位是1 ((b & 0x80) != 0),就將其低7位拼接到結果中,并增加位移量?i。當讀到最高位為0的字節時,循環結束。

writeVarlong

此方法的作用是將一個64位的有符號長整型 (long) 編碼后寫入到一個?DataOutput?輸出流中。這個編碼過程與我們之前討論的?writeVarint?非常相似,同樣是?ZigZag(對角線)編碼?和?Varint 編碼?的組合,只不過這次是針對64位的?long?類型。

// ... existing code .../*** Write the given integer following the variable-length zig-zag encoding from* <a href="http://code.google.com/apis/protocolbuffers/docs/encoding.html"> Google Protocol Buffers</a>* into the output.** @param value The value to write* @param out The output to write to*/public static void writeVarlong(long value, DataOutput out) throws IOException {long v = (value << 1) ^ (value >> 63);while ((v & 0xffffffffffffff80L) != 0L) {out.writeByte(((int) v & 0x7f) | 0x80);v >>>= 7;}out.writeByte((byte) v);}
// ... existing code ...

整個方法的執行可以分為兩個主要步驟:

ZigZag(對角線)編碼

long v = (value << 1) ^ (value >> 63);

這是編碼的第一步,也是核心的 ZigZag 編碼步驟。

  • value << 1: 將原始的?long?值向左移動一位。這個操作的目的是為符號位騰出空間。
  • value >> 63: 這是一個算術右移操作。對于?long?類型,算術右移63位會得到一個全為符號位的值。如果?value?是正數或0,結果是?0L;如果?value?是負數,結果是?-1L?(二進制?0xFFFFFFFFFFFFFFFF)。
  • ^: 異或操作。
    • 如果?value?是正數或0:?(value << 1) ^ 0,結果就是?value?的兩倍。
    • 如果?value?是負數:?(value << 1) ^ -1,結果是對?value?左移一位后的值進行按位取反。

這個公式巧妙地將有符號的?long?映射到了無符號的?long?數軸上,實現了我們之前討論過的“對角線”映射,確保了絕對值小的數(無論正負)都會得到一個小的無符號編碼值?v

Varint 編碼

接下來的?while?循環負責將上一步得到的無符號編碼值?v?進行 Varint 編碼,并逐字節寫入輸出流。

while ((v & 0xffffffffffffff80L) != 0L) {out.writeByte(((int) v & 0x7f) | 0x80);v >>>= 7;
}
out.writeByte((byte) v);
  • while ((v & 0xffffffffffffff80L) != 0L): 這是循環的條件。0xffffffffffffff80L?是一個掩碼,它的低7位是0,其余位都是1。這個條件檢查?v?是否還有超過7位的數據。換句話說,只要?v?的值大于等于 128 (2^7),循環就會繼續。

  • out.writeByte(((int) v & 0x7f) | 0x80);: 這是循環體內的核心操作。

    • (int) v & 0x7f: 取出?v?的低7位數據。
    • | 0x80: 將這7位數據與?0x80?(二進制?10000000) 進行或運算。這會將該字節的最高位(MSB)設置為?1,表示后面還有更多的字節。
    • out.writeByte(...): 將這個構造好的字節寫入輸出流。
  • v >>>= 7;: 將?v?無符號右移7位,準備處理下一組7位數據。

  • out.writeByte((byte) v);: 當循環結束時,意味著?v?的剩余值已經小于128,可以用7位來表示。這時,將?v?的最后部分直接作為一個字節寫入。這個字節的最高位自然是?0,表示這是 Varint 序列的最后一個字節。

總結

writeVarlong?方法通過一個兩步過程高效地序列化一個?long?值:

  1. ZigZag 編碼: 使用?(value << 1) ^ (value >> 63)?將有符號?long?映射為無符號?long,使得小數值(無論正負)編碼后依然是小數值。
  2. Varint 編碼: 使用?while?循環,每次從編碼后的值中取出7位數據,并加上一個“續傳”標志位(MSB=1),然后寫入字節流,直到最后不足7位的數據作為最后一個字節(MSB=0)寫入。

這種組合編碼方式是 Kafka 協議中節省空間、提升效率的關鍵技術之一,尤其在傳輸大量包含小整數(如時間戳增量、偏移量增量等)的消息時效果顯著。

無符號整數(Unsigned Integers)的處理

Java 的基本數據類型中沒有無符號整數(unsigned int)。但在網絡協議或與其他系統交互時,經常需要處理無符號數。ByteUtils?提供了方法來模擬對 32 位無符號整數的讀寫。

讀取無符號整數

為了避免將一個最高位為1的32位整數錯誤地解釋為負數,ByteUtils?在讀取后將其轉換為?long?類型。

// ... existing code .../*** Read an unsigned integer from the current position in the buffer, incrementing the position by 4 bytes** @param buffer The buffer to read from* @return The integer read, as a long to avoid signedness*/public static long readUnsignedInt(ByteBuffer buffer) {return buffer.getInt() & 0xffffffffL;}
// ... existing code ...
  • 代碼分析:?buffer.getInt()?讀取一個標準的 32 位有符號整數。關鍵在于?& 0xffffffffL?這個操作。它是一個按位與操作,通過一個?long?類型的掩碼,將讀取到的?int?值(可能會被當作負數)轉換為一個正的?long?值,從而正確地表示了原始的無符號整數值。

寫入無符號整數

寫入時,邏輯類似,將一個?long?值截斷為 32 位?int?再寫入。

// ... existing code .../*** Write the given long value as a 4 byte unsigned integer. Overflow is ignored.** @param buffer The buffer to write to* @param value The value to write*/public static void writeUnsignedInt(ByteBuffer buffer, long value) {buffer.putInt((int) (value & 0xffffffffL));}
// ... existing code ...
  • 代碼分析:?(value & 0xffffffffL)?確保只取?long?值的低 32 位,然后強制轉換為?int?并寫入?ByteBuffer

此外,該類還提供了處理小端字節序(Little-Endian)的方法,如?readUnsignedIntLE?和?writeUnsignedIntLE,這在需要與采用不同字節序的系統交互時非常有用。Kafka 的網絡協議本身是網絡字節序,即大端(Big-Endian)。

其他工具方法

除了上述核心功能,ByteUtils?還包含一些其他有用的方法,例如:

  • readDouble/writeDouble: 讀寫 64 位浮點數。
  • EMPTY_BUF: 提供一個靜態的、空的?ByteBuffer?實例,避免重復創建。

總結

ByteUtils?是 Kafka 中一個至關重要的底層工具類,它封裝了對 Java 基本類型與字節之間進行高效轉換的邏輯。它的設計體現了對性能的極致追求,例如在?readUnsignedVarint?中使用循環展開,以及提供 Varint/Varlong 這種空間高效的編碼方式。理解這個類的工作原理,特別是 Varint 和 ZigZag 編碼,對于深入理解 Kafka 的網絡協議、消息格式以及存儲機制非常有幫助。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/90575.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/90575.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/90575.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

局域網 IP地址

很多童鞋搞不清楚局域網ip是什么? 什么是局域網 IP 地址? 局域網 IP 地址,也稱為 私有 IP 地址(Private IP Address),是用于在局域網內部標識設備的地址。這些地址不能直接在互聯網上被訪問,通常由路由器自動分配,用于設備之間的內部通信。 局域網 IP 地址的分類 根…

k8s的service、deployment、探針詳解

1.k8s組成圖2.service和deployment的流量轉發圖# Deployment 定義容器端口 apiVersion: apps/v1 kind: Deployment metadata:name: myapp spec:template:spec:containers:- name: nginximage: nginxports:- containerPort: 80 # 容器監聽 80name: http # 端口命名&…

【PostgreSQL教程】PostgreSQL中json類型與jsonb類型的區別

博主介紹:?全網粉絲23W+,CSDN博客專家、Java領域優質創作者,掘金/華為云/阿里云/InfoQ等平臺優質作者、專注于Java技術領域? 技術范圍:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大數據、物聯網、機器學習等設計與開發。 感興趣的可…

牛客刷題記錄01

除2&#xff01; 目錄 除2&#xff01; 題目描述&#xff1a; ?編輯 題目解析&#xff1a; 代碼實現&#xff1a; 數組中兩個字符串的最小距離__牛客網 題目描述&#xff1a; 題目解析&#xff1a; 代碼實現&#xff1a; 除2&#xff01; 題目描述&#xff1a; 給一個…

Docker Compose UI遠程訪問教程:結合貝銳花生殼實現內網穿透

對于很多剛接觸Docker的用戶來說&#xff0c;命令行操作總帶著一絲“勸退感”。尤其是要在Windows上部署服務、開放端口、配置參數時&#xff0c;稍有不慎就容易出錯。有沒有辦法像網頁后臺一樣&#xff0c;用圖形界面來管理Docker項目呢&#xff1f;答案是&#xff1a;有&…

HF83311_VB1/HF83311Q_VB1:高性能USB HiFi音頻解碼器固件技術解析

引言隨著高品質音頻體驗需求的不斷增長&#xff0c;音頻解碼器固件的性能和功能成為決定音頻設備品質的關鍵因素。本文將介紹一款基于XMOS XU316技術的高性能USB HiFi音頻解碼器固件——HF83311_VB1/HF83311Q_VB1&#xff0c;這是一款專為USB HiFi音頻應用設計的軟件解決方案。…

[ComfyUI] -入門1-ComfyUI 是什么?比 Stable Diffusion WebUI 強在哪?

ComfyUI 是一個開源的、節點可視化界面,用于構建與執行 Stable Diffusion 圖像生成流程。它把復雜的生成過程拆解為許多“節點”(如提示編碼、采樣器、控制網絡等),用戶通過連接節點,就能自由編排工作流 。這種設計適合開發者與進階用戶,更便于微調、多分支與復用流程。 …

[python][flask]flask接受get或者post參數

在 Flask 中&#xff0c;可以通過 request 對象來獲取客戶端通過 GET 或 POST 方法發送的參數。以下是如何在 Flask 中接收 GET 和 POST 參數的詳細說明&#xff1a;1. 接收 GET 參數GET 請求的參數通常通過 URL 的查詢字符串傳遞。例如&#xff0c;對于 URL http://example.co…

Creo 模塊眾多,企業如何按需靈活分配許可證資源?

在數字化設計與智能制造深入發展的當下&#xff0c;企業 CAD/CAE 工具的精細化管理越來越重要。Creo&#xff0c;作為 PTC 旗下一體化 3D CAD 平臺&#xff0c;以其模塊化、可擴展的產品架構&#xff0c;廣泛應用于機械、裝備、汽車、航空航天等行業。其豐富的模塊庫覆蓋建模設…

【c++】提升用戶體驗:問答系統的交互優化實踐——關于我用AI編寫了一個聊天機器人……(12)

本期依舊使用豆包輔助完成代碼。從功能到體驗的轉變上個版本已經實現了問答系統的核心功能&#xff1a;基于 TF-IDF 算法的問題匹配和回答。它能夠讀取訓練數據&#xff0c;處理用戶輸入&#xff0c;并返回最相關的答案。但在用戶體驗方面還有很大提升空間。讓我們看看改進版做…

Android UI 控件詳解實踐

一、UI 開發基礎概念&#xff08;初學者必看&#xff09; 在學習具體控件前&#xff0c;先理解以下核心概念&#xff0c;能大幅降低后續學習難度&#xff1a; 1. View 與 ViewGroup 的關系 View&#xff1a;所有 UI 控件的基類&#xff08;如 Button、TextView&#xff09;&…

關于linux運維 出現高頻的模塊認知

一、Linux 基礎核心&#xff08;必掌握&#xff09;核心工具&#xff1a;Shell 腳本、Systemd、用戶權限管理、日志分析&#xff08;journalctl、rsyslog&#xff09;企業需求&#xff1a;中小型公司&#xff1a;需獨立完成系統部署、故障排查&#xff0c;對腳本開發&#xff0…

手語式映射:Kinova Gen3 力控機械臂自適應控制的研究與應用

近日&#xff0c;美國明尼蘇達大學研究團隊在《從人手到機械臂&#xff1a;遙操作中運動技能具身化研究》中&#xff0c;成功開發出基于??Kinova的7軸力控機械臂Gen3的智能控制系統。這項創新性技術通過人工智能算法&#xff0c;實現了人類手臂動作到機械臂運動的精準映射&am…

P5535 【XR-3】小道消息

題目描述 小 X 想探究小道消息傳播的速度有多快&#xff0c;于是他做了一個社會實驗。 有 n 個人&#xff0c;其中第 i 個人的衣服上有一個數 i1。小 X 發現了一個規律&#xff1a;當一個衣服上的數為 i 的人在某一天知道了一條信息&#xff0c;他會在第二天把這條信息告訴衣…

ChatGPT Agent架構深度解析:OpenAI如何構建統一智能體系統

引言&#xff1a;AI智能體的范式躍遷 2025年7月17日&#xff0c;OpenAI發布的ChatGPT Agent標志著對話式AI從“被動應答”向主動執行的歷史性轉變。這款融合Operator網頁操作與Deep Research信息分析能力的新型智能體&#xff0c;通過統一架構設計實現了復雜任務的端到端自主執…

計算機網絡(第八版)— 第2章課后習題參考答案

2-01 物理層要解決哪些問題&#xff1f;物理層的主要特點是什么&#xff1f;答&#xff1a;物理層要解決的主要問題&#xff1a;&#xff08;1&#xff09;物理層要盡可能地屏蔽掉物理設備和傳輸媒體&#xff0c;通信手段的不同&#xff0c;使數據鏈路層感覺不到這些差異&#…

Hive【Hive架構及工作原理】

?博客主頁&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客內容》&#xff1a;.NET、Java.測試開發、Python、Android、Go、Node、Android前端小程序等相關領域知識 &#x1f4e2;博客專欄&#xff1a; https://blog.csdn.net/m0_63815035/cat…

數據管理能力成熟度評估模型(DCMM)詳解

數據管理能力成熟度評估模型(DCMM)詳解 1. DCMM概述 數據管理能力成熟度評估模型(Data Management Capability Maturity Assessment Model, DCMM)是我國首個數據管理領域的國家標準(GB/T 36073-2018)&#xff0c;由國家工業信息安全發展研究中心牽頭制定。該模型為我國企業數據…

學習C++、QT---34(使用QT庫實現串口調試助手01:解決串口調試助手的UI)

&#x1f31f; 嗨&#xff0c;我是熱愛嵌入式的濤濤同學&#xff01;每日一言別害怕改變&#xff0c;走出舒適圈才能遇見更好的自己。串口調試助手項目好的現在我們來學習串口調試助手的項目&#xff0c;我們依舊是項目引領學習好的我們最后就是要做成一個類似我們市面上的串口…

Dockerfile 文件及指令詳解

什么是Dockerfile 文件Dockerfile 文件是用于構建 docker 鏡像的腳本文件&#xff0c;由一系列的指令構成。通過 docker build 命令構建鏡像時&#xff0c;Dockerfile 文件中的指令會由上到下執行&#xff0c;每條 指令都將會構建出一個鏡像層&#xff0c;這就是鏡像的分層。因…