嵌入式調試LOG日志輸出(以STM32為例)

引言

在嵌入式系統開發中,調試是貫穿整個生命周期的關鍵環節。與傳統PC端程序不同,嵌入式設備資源受限(如內存、存儲、處理器性能),且運行環境復雜(無顯示器、鍵盤),傳統的斷點調試或打印到控制臺的方式往往難以滿足實時性、便捷性需求。此時,??日志系統(LOG)?? 成為嵌入式調試的核心工具——它通過將關鍵運行信息輸出到外部設備(如串口),幫助開發者快速定位問題、跟蹤程序狀態。

本文將以STM32F103系列單片機為例,結合實際工程實踐,介紹一款輕量、靈活、易集成的日志系統設計與實現,涵蓋日志分級、格式控制、串口輸出等核心功能,并通過示例演示其在嵌入式調試中的具體應用。


嵌入式日志系統的核心需求

嵌入式場景下,日志系統需滿足以下核心需求:

1. ??資源友好性??

STM32的內存(如STM32F103C8T6僅有20KB SRAM)和Flash空間有限,日志系統需避免占用過多資源。例如,日志緩沖區需固定大小(如256字節),避免動態內存分配;輸出函數需輕量(如直接調用串口發送)。

2. ??分級控制??

不同調試階段需要關注不同詳細程度的信息。例如:

  • ??開發階段??:需要詳細的函數調用、變量值(TRACE/DEBUG級別);
  • ??測試階段??:關注關鍵流程狀態(INFO/WARN級別);
  • ??發布階段??:僅保留錯誤信息(ERROR/FATAL級別)。
    因此,日志系統需支持??級別過濾??,通過配置只輸出高于設定級別的日志。

3. ??格式靈活性??

日志需包含足夠的上下文信息以輔助調試,但冗余信息會干擾閱讀。常見的日志要素包括:

  • ??級別標識??(如[TRACE]/[ERROR]):快速區分日志嚴重程度;
  • ??時間戳??(如[14:23:45.678]):定位問題發生時刻;
  • ??函數名+行號??(如[main:45]):追蹤代碼執行路徑;
  • ??原始消息??(如“文件打開失敗”):具體問題描述。
    日志系統需支持??格式配置??,允許用戶按需組合上述要素。

4. ??高效輸出??

嵌入式系統的串口帶寬有限(如常見的115200bps,約11.5KB/s),日志輸出需避免阻塞主程序。例如,采用非阻塞發送(或短時間阻塞)、控制單次輸出數據量(不超過串口發送緩沖區)。


日志系統設計實現

基于上述需求,我們設計了一款基于STM32 HAL庫的日志系統,核心功能包括日志分級、格式控制、串口輸出,以下是關鍵模塊的實現細節。

1. 日志級別定義

日志級別采用枚舉類型定義,從低到高依次為TRACEDEBUGINFOWARNERRORFATAL,數值越小優先級越高。通過級別過濾,可靈活控制日志輸出范圍:

typedef enum {LOG_LEVEL_TRACE = 0,  // 最低級別,用于最詳細的跟蹤信息LOG_LEVEL_DEBUG,      // 調試信息,開發階段使用LOG_LEVEL_INFO,       // 重要狀態信息,測試階段使用LOG_LEVEL_WARN,       // 警告信息,提示潛在問題LOG_LEVEL_ERROR,      // 錯誤信息,功能異常但可恢復LOG_LEVEL_FATAL,      // 嚴重錯誤,系統可能崩潰LOG_LEVEL_MAX         // 枚舉結束標志
} log_level_t;

2. 日志格式控制

日志格式通過宏定義控制,支持按位或組合多種要素:

#define LOG_FMT_RAW           (0u)          // 僅原始消息(無額外信息)
#define LOG_FMT_LEVEL_STR     (1u << 0)      // 級別字符串(如[TRACE])
#define LOG_FMT_TIME_STAMP    (1u << 1)      // 時間戳(如[14:23:45.678])
#define LOG_FMT_FUNC_LINE     (1u << 2)      // 函數名+行號(如[main:45])

用戶可通過Ulog_SetFmt()函數動態配置格式(例如LOG_FMT_LEVEL_STR | LOG_FMT_TIME_STAMP表示輸出級別和時間戳)。

3. 核心日志函數實現

日志系統的核心是Ulog()函數,負責格式化日志內容并輸出。其流程如下:

(1)級別過濾

首先檢查當前日志級別是否低于設定的最低輸出級別(如設置為LOG_LEVEL_INFO時,TRACEDEBUG日志會被過濾)。

(2)緩沖區初始化

使用固定大小的緩沖區(如256字節)存儲日志內容,避免動態內存分配帶來的風險。

(3)格式化要素拼接

根據配置的格式,依次拼接級別字符串、時間戳、函數名+行號等信息。例如:

  • 級別字符串通過level_str數組映射(如LOG_LEVEL_TRACE對應"[TRACE]");
  • 時間戳基于HAL_GetTick()獲取系統運行時間(毫秒級),格式化為[HH:MM:SS.xxx]
  • 函數名+行號通過__func__(編譯器內置宏)和__LINE__(行號宏)獲取,并截斷過長函數名(避免緩沖區溢出)。
(4)日志內容填充

使用va_list處理可變參數,將用戶輸入的日志消息格式化到緩沖區中。

(5)輸出日志

通過注冊的輸出函數(默認使用串口)將緩沖區內容發送到外部設備。

void Ulog(uint32_t level, const char *func, uint32_t line, const char *fmt, ...) {// 1. 級別過濾if (level >= LOG_LEVEL_MAX || level < s_ulog_level) return;// 2. 緩沖區初始化char log_buf[CONFIG_ULOG_BUF_SIZE] = {0};va_list args;int idx = 0;// 3. 拼接級別字符串(如[TRACE])if (s_ulog_fmt & LOG_FMT_LEVEL_STR) {static const char *level_str[] = {"TRACE", "DEBUG", "INFO ", "WARN ", "ERROR", "FATAL"};idx += snprintf(log_buf + idx, sizeof(log_buf)-idx, "[%s] ", level_str[level]);}// 4. 拼接時間戳(如[14:23:45.678])if (s_ulog_fmt & LOG_FMT_TIME_STAMP) {char time_buf[32];uint16_t ms = 0;Get_SystemTime(time_buf, sizeof(time_buf), &ms);  // 基于HAL_GetTick獲取時間idx += snprintf(log_buf + idx, sizeof(log_buf)-idx, "[%s.%03d] ", time_buf, ms);}// 5. 拼接函數名+行號(如[main:45])if (s_ulog_fmt & LOG_FMT_FUNC_LINE) {char short_func[20] = {0};strncpy(short_func, func, sizeof(short_func)-1);  // 截斷過長函數名idx += snprintf(log_buf + idx, sizeof(log_buf)-idx, "[%s:%d] ", short_func, (int)line);}// 6. 填充日志內容(可變參數)va_start(args, fmt);int len = vsnprintf(log_buf + idx, sizeof(log_buf)-idx, fmt, args);  // 格式化消息va_end(args);if (len > 0) idx += len;  // 有效內容則更新索引// 7. 添加換行符(STM32串口常用\r\n)if (idx < CONFIG_ULOG_BUF_SIZE - 2) {snprintf(log_buf + idx, sizeof(log_buf)-idx, "%s", ULOG_NEWLINE_SIGN);idx += strlen(ULOG_NEWLINE_SIGN);}// 8. 輸出日志(調用注冊的串口發送函數)ulog_output((uint8_t *)log_buf, (uint16_t)idx);
}

4. 串口輸出實現

STM32的串口輸出通過HAL庫實現,核心是Uart_SendData()函數,利用HAL_UART_Transmit()發送數據。為避免阻塞,設置超時時間(如100ms):

// 串口句柄(需在stm32f1xx_hal_conf.h中啟用USART1)
extern UART_HandleTypeDef UartHandle;// 串口數據發送函數
static void Uart_SendData(uint8_t *data, uint16_t size) {if (huart1.Instance != NULL) {HAL_UART_Transmit(&UartHandle, data, size, 100);  // 超時100ms}
}

5. 全局配置與接口

通過全局變量管理日志配置(如當前級別、格式、輸出函數),并提供接口供用戶動態修改:

// 全局配置
static uint32_t s_ulog_fmt = LOG_FMT_LEVEL_STR | LOG_FMT_TIME_STAMP | LOG_FMT_FUNC_LINE;  // 默認格式
static uint32_t s_ulog_level = CONFIG_ULOG_DEF_LEVEL;                                   // 默認級別(TRACE)
static UlogOutputFunc ulog_output = NULL;                                               // 默認輸出函數// 注冊輸出函數(默認使用串口)
void Ulog_RegisterOutput(UlogOutputFunc func) {ulog_output = func ? func : Uart_SendData;  // 未注冊時使用串口
}// 設置日志級別
int Ulog_SetLevel(uint32_t level) {if (level >= LOG_LEVEL_MAX) return -1;s_ulog_level = level;return 0;
}// 設置日志格式
void Ulog_SetFmt(uint32_t fmt) {s_ulog_fmt = fmt;
}


日志系統使用示例

以下通過一個完整的測試用例,演示日志系統的實際效果。

1. 工程配置

  • ??硬件連接??:STM32F103 USART1(PA9-TX,PA10-RX)接USB轉串口模塊(波特率115200,8-N-1);
  • ??軟件配置??:在main.c中初始化HAL庫、系統時鐘、USART1,并注冊串口輸出函數。

2. 測試代碼

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx.h"
#include "./usart/bsp_debug_usart.h"
#include "./log/log.h"  /* 測試函數 */
void Test_LogFunctions(void) 
{LOG_TRACE("開始測試日志功能");LOG_DEBUG("調試信息 - 變量值: %d", 100);LOG_INFO("系統初始化完成");LOG_WARN("內存使用率高達85%%");LOG_ERROR("文件打開失敗: %s", "test.log");LOG_FATAL("核心模塊初始化失敗,系統即將終止");
}void Test_LogRunTimeDebug(void)
{static uint32_t u32Cnt = 0;u32Cnt++;LOG_DEBUG("系統運行中,%04d", u32Cnt);
}int main(void) 
{HAL_Init();        /* 配置系統時鐘為72 MHz */ SystemClock_Config();/*初始化USART 配置模式為 115200 8-N-1,中斷接收*/DEBUG_USART_Config();/* 注冊串口輸出函數 */Ulog_RegisterOutput(Uart_SendData);/* 測試完整格式日志 (級別+時間+行號) */Ulog_SetFmt(LOG_FMT_LEVEL_STR | LOG_FMT_TIME_STAMP | LOG_FMT_FUNC_LINE);printf("=== 測試完整格式日志 (級別+時間+行號) ===\r\n");Test_LogFunctions();/* 測試基本格式(級別+時間) */Ulog_SetFmt(LOG_FMT_LEVEL_STR | LOG_FMT_TIME_STAMP);printf("\r\n=== 測試基本格式(級別+時間) ===\r\n");Test_LogFunctions();/* 測試基本格式(級別) */Ulog_SetFmt(LOG_FMT_LEVEL_STR);printf("\r\n=== 測試基本格式(級別) ===\r\n");Test_LogFunctions();/* 測試原始格式(僅消息內容) */Ulog_SetFmt(LOG_FMT_RAW);printf("\r\n=== 測試原始格式(僅消息內容) ===\r\n");Test_LogFunctions();//顯示運行時Debug數據Ulog_SetFmt(LOG_FMT_LEVEL_STR | LOG_FMT_TIME_STAMP | LOG_FMT_FUNC_LINE);printf("\r\n=== 顯示運行時Debug數據 ===\r\n");while(1) {HAL_Delay(1000);  // 主循環保持運行Test_LogRunTimeDebug();}
}


完整代碼

log.h

#ifndef __LOG_H
#define	__LOG_H#include "stm32f1xx.h"
#include "stm32f1xx_hal.h"
#include <stdarg.h>
#include <stdint.h>
#include <string.h>/* 配置宏定義 */
#define CONFIG_ULOG_BUF_SIZE    256u
#define CONFIG_ULOG_DEF_LEVEL   LOG_LEVEL_TRACE
#define ULOG_NEWLINE_SIGN       "\r\n"  // STM32串口常用換行符/* 日志級別枚舉 */
typedef enum {LOG_LEVEL_TRACE = 0,LOG_LEVEL_DEBUG,LOG_LEVEL_INFO,LOG_LEVEL_WARN,LOG_LEVEL_ERROR,LOG_LEVEL_FATAL,LOG_LEVEL_MAX
} log_level_t;/* 格式控制宏 */
#define LOG_FMT_RAW           (0u)
#define LOG_FMT_LEVEL_STR     (1u << 0)
#define LOG_FMT_TIME_STAMP    (1u << 1)
#define LOG_FMT_FUNC_LINE     (1u << 2)/* 啟用日志級別開關 */
#define LOG_TRACE_EN  1
#define LOG_DEBUG_EN  1
#define LOG_INFO_EN   1
#define LOG_WARN_EN   1
#define LOG_ERROR_EN  1
#define LOG_FATAL_EN  1/* 日志宏定義 */
#if LOG_TRACE_EN
#define LOG_TRACE(fmt, ...)   Ulog(LOG_LEVEL_TRACE, __func__, __LINE__, fmt, ##__VA_ARGS__)
#else
#define LOG_TRACE(fmt, ...)
#endif#if LOG_DEBUG_EN
#define LOG_DEBUG(fmt, ...)   Ulog(LOG_LEVEL_DEBUG, __func__, __LINE__, fmt, ##__VA_ARGS__)
#else
#define LOG_DEBUG(fmt, ...)
#endif#if LOG_INFO_EN
#define LOG_INFO(fmt, ...)    Ulog(LOG_LEVEL_INFO, __func__, __LINE__, fmt, ##__VA_ARGS__)
#else
#define LOG_INFO(fmt, ...)
#endif#if LOG_WARN_EN
#define LOG_WARN(fmt, ...)    Ulog(LOG_LEVEL_WARN, __func__, __LINE__, fmt, ##__VA_ARGS__)
#else
#define LOG_WARN(fmt, ...)
#endif#if LOG_ERROR_EN
#define LOG_ERROR(fmt, ...)   Ulog(LOG_LEVEL_ERROR, __func__, __LINE__, fmt, ##__VA_ARGS__)
#else
#define LOG_ERROR(fmt, ...)
#endif#if LOG_FATAL_EN
#define LOG_FATAL(fmt, ...)   Ulog(LOG_LEVEL_FATAL, __func__, __LINE__, fmt, ##__VA_ARGS__)
#else
#define LOG_FATAL(fmt, ...)
#endif/* 日志輸出函數類型 */
typedef void (*UlogOutputFunc)(uint8_t *data, uint16_t size);extern void Ulog_RegisterOutput(UlogOutputFunc func);
extern void Ulog_SetFmt(uint32_t fmt);
extern void Ulog(uint32_t level, const char *func, uint32_t line, const char *fmt, ...);#endif /* __LOG_H */

?log.c

#include "./usart/bsp_debug_usart.h"
#include "./log/log.h"   /* 全局配置 */
static uint32_t s_ulog_fmt = LOG_FMT_LEVEL_STR | LOG_FMT_TIME_STAMP | LOG_FMT_FUNC_LINE;
static uint32_t s_ulog_level = CONFIG_ULOG_DEF_LEVEL;
static UlogOutputFunc ulog_output = NULL;static void Get_SystemTime(char *time_buf, uint16_t buf_size, uint16_t *ms);/* 注冊輸出函數(默認使用串口) */
void Ulog_RegisterOutput(UlogOutputFunc func) 
{ulog_output = func ? func : Uart_SendData;
}/* 設置日志格式 */
void Ulog_SetFmt(uint32_t fmt) 
{s_ulog_fmt = fmt;
}/* 系統時間獲取(基于HAL_GetTick) */
static void Get_SystemTime(char *time_buf, uint16_t buf_size, uint16_t *ms) {uint32_t tick = HAL_GetTick();  // 獲取系統運行時間(毫秒)*ms = tick % 1000;uint32_t sec = tick / 1000;uint32_t hour = sec / 3600;uint32_t min = (sec % 3600) / 60;sec = sec % 60;snprintf(time_buf, buf_size, "%02d:%02d:%02d", (int)(hour % 24), (int)min, (int)sec);
}/* 核心日志函數 */
void Ulog(uint32_t level, const char *func, uint32_t line, const char *fmt, ...) 
{/* 級別過濾 */if (level >= LOG_LEVEL_MAX || level < s_ulog_level) return;/* 緩沖區初始化 */char log_buf[CONFIG_ULOG_BUF_SIZE] = {0};va_list args;int idx = 0;/* 級別字符串 */if (s_ulog_fmt & LOG_FMT_LEVEL_STR) {static const char *level_str[] = {"TRACE", "DEBUG", "INFO ", "WARN ", "ERROR", "FATAL"};idx += snprintf(log_buf + idx, sizeof(log_buf)-idx, "[%s] ", level_str[level]);}/* 時間戳 */if (s_ulog_fmt & LOG_FMT_TIME_STAMP) {char time_buf[32];uint16_t ms = 0;Get_SystemTime(time_buf, sizeof(time_buf), &ms);idx += snprintf(log_buf + idx, sizeof(log_buf)-idx, "[%s.%03d] ", time_buf, ms);}/* 函數名+行號 */if (s_ulog_fmt & LOG_FMT_FUNC_LINE) {char short_func[20] = {0};strncpy(short_func, func, sizeof(short_func)-1);idx += snprintf(log_buf + idx, sizeof(log_buf)-idx, "[%s:%d] ", short_func, (int)line);}/* 日志內容 */va_start(args, fmt);int len = vsnprintf(log_buf + idx, sizeof(log_buf)-idx, fmt, args);va_end(args);/* 處理格式化錯誤 */if (len < 0) {idx += snprintf(log_buf + idx, sizeof(log_buf)-idx, "[LOG FORMAT ERROR]");} else if (len > 0) {idx += len;}/* 添加換行符 */if (idx < CONFIG_ULOG_BUF_SIZE - 2) {snprintf(log_buf + idx, sizeof(log_buf)-idx, "%s", ULOG_NEWLINE_SIGN);idx += strlen(ULOG_NEWLINE_SIGN);}/* 輸出日志 */ulog_output((uint8_t *)log_buf, (uint16_t)idx);
}/*********************************************END OF FILE**********************/

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

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

相關文章

Zephyr的設備驅動模型

默認配置默認配置 boards/arm/nucleo_f401re/ ├── nucleo_f401re.dts ← 板卡設備樹主入口 ├── nucleo_f401re_defconfig ← 默認 Kconfig 配置 ├── board.cmake ← CMake 構建入口overlay1.新增加驅動需要修改對應板的設備樹文件&#xf…

Mysql字段沒有索引,通過where x = 3 for update是使用什么級別的鎖

沒有索引時&#xff0c;FOR UPDATE 會鎖住整個表 現在&#xff0c;你正在一本一本地翻看所有書&#xff0c;尋找“維修中”的書&#xff0c;并且你對管理員說&#xff1a;“在我清點和修改完之前&#xff0c;別人不能動這些書&#xff0c;也不能往這個范圍里加新書&#xff01;…

TCP-與-UDP-協議詳解:原理、區別與應用場景全解析

TCP 與 UDP 協議詳解&#xff1a;原理、區別與應用場景全解析 在日常使用網絡的過程中&#xff0c;我們經常聽到 TCP 和 UDP 這兩個詞。你打開網頁、發送消息、觀看視頻&#xff0c;背后都在使用 TCP 或 UDP 進行數據傳輸。那么這兩個協議到底是怎么工作的&#xff1f;它們之間…

GitHub信息收集

目錄 簡介 一、入門搜索技巧 1. 基本關鍵詞搜索 2. 文件類型限定搜索 3. 用戶/組織定向搜索 二、精準定位技巧 1. 組合搜索條件 2. 排除干擾結果 3. 路徑限定搜索 三、防御建議 四、法律與道德提醒 簡介 GitHub作為全球最大的代碼托管平臺&#xff0c;存儲著數十億…

由 DB_FILES 參數導致的 dg 服務器無法同步問題

由 DB_FILES 參數導致的 dg 服務器無法同步問題 用戶反映&#xff0c;dg 服務器數據從昨晚&#xff08;7月8日&#xff09;開始停止同步。 連接服務器發現沒有 mrp 進程&#xff0c;并且 OPEN_MODE 參數也不正確。具體情況如下所示&#xff1a; SQL> select process, status…

Go語言泛型-泛型對代碼結構的優化

在Go語言中,Go泛型-泛型對代碼結構的優化部分主要探討了泛型如何幫助我們優化代碼結構、減少重復代碼,并提高代碼的可維護性、可讀性和復用性。以下是詳細內容: 一、引言 Go 1.18 引入了泛型,極大地提高了語言的靈活性。泛型使得我們可以編寫更加通用、可復用且類型安全的…

【1-快速上手】

文章目錄前言簡介什么是 Konva&#xff1f;安裝 Konva概述它是如何工作的&#xff1f;基本形狀樣式事件拖放濾鏡動畫選擇器序列化與反序列化性能前言 結合項目實際業務需求&#xff0c;在 Fabric、Konva 等圖形化框架中&#xff0c;我選擇了性能表現好的 Konva。首先去學習官方…

【LeetCode】209. 長度最小的子數組(前綴和 + 二分)

【LeetCode】209. 長度最小的子數組&#xff08;前綴和 二分&#xff09;題目描述前綴和二分優化前綴和總結二分總結題目描述 題目鏈接&#xff1a;【LeetCode】209. 長度最小的子數組&#xff08;前綴和 二分&#xff09; 給定一個含有 n 個整數的數組和一個整數 target。…

文件系統----底層架構

當我們談到文件系統的時候&#xff0c;最重要的點在于&#xff1a;文件的內容與屬性是如何存儲在磁盤中的&#xff1f;以及操作系統是如何精準定位到這些文件內容的&#xff1f;在談及文件的內核前&#xff0c;我們先來了解一下儲存文件的硬件-----硬盤一.理解硬件首先我們來看…

小程序開發平臺,自主開發小程序源碼系統,多端適配,帶完整的部署教程

溫馨提示&#xff1a;文末有資源獲取方式全開源與自主開發源碼完全開放&#xff1a;開發者可自由修改前端界面、后端邏輯及數據庫結構&#xff0c;支持深度定制&#xff08;如調整用戶端交互流程、商家端管理功能等&#xff09;。技術棧透明&#xff1a;基于主流技術&#xff0…

stp拓撲變化分類

Max Age 20sHellotime 2sForward delay 153、拓撲改變需要多長時間1&#xff09;根橋故障&#xff1a;需要50秒&#xff08;Max age2個forwarding delay&#xff09;2&#xff09;非直連鏈路&#xff1a;非直連故障在穩定的STP網絡&#xff0c;非根橋會定期收到來自根橋的BPDU報…

一、深度學習——神經網絡

一、神經網絡 1.神經網絡定義&#xff1a;人工神經網絡&#xff08;Artificial Neural Network&#xff0c;ANN&#xff09;也簡稱為神經網絡&#xff08;NN&#xff09;&#xff0c;是一種模仿生物神經網絡結構和功能的計算模型。人腦可以看作是一個生物神經網絡&#xff0c;由…

【牛客算法】 小紅的奇偶抽取

文章目錄 一、題目介紹1.1 題目描述1.2 輸入描述1.3 輸出描述1.4 示例二、解題思路2.1 核心算法設計2.2 性能優化關鍵2.3 算法流程圖三、解法實現3.1 解法一:字符串分離法3.1.1 初級版本分析3.2 解法二:數學逐位構建法(推薦)3.2.1 優化版本分析四、總結與拓展4.1 關鍵優化技…

Maven 繼承:構建高效項目結構的利器

一、引言 Maven 是一個強大的項目管理工具&#xff0c;它通過標準化的項目結構和依賴管理極大地簡化了 Java 項目的開發流程。在 Maven 中&#xff0c;繼承是一種非常有用的功能&#xff0c;它允許我們創建一個父項目&#xff0c;其他子項目可以繼承這個父項目的配置信息&#…

Mysql組合索引的update在多種情況下的間隙鎖的范圍(簡單來說)

簡單來說&#xff0c;當 UPDATE 語句的 WHERE 條件使用了組合索引&#xff0c;并且需要鎖定不存在的“間隙”來防止幻讀時&#xff0c;就會產生間隙鎖。間隙鎖的范圍取決于 WHERE 條件如何利用組合索引&#xff0c;以及數據庫的隔離級別。 我們用圖書館的例子。比如&#xff1a…

什么是Apache Ignite的affinity(親和性)

在 Apache Ignite 中&#xff0c; affinity&#xff08;親和性&#xff09; 是一種用于控制數據分布和查詢性能的重要機制。它允許開發者指定數據如何在集群中的節點之間分布&#xff0c;從而優化數據訪問和查詢效率。以下是關于 affinity 的詳細解釋&#xff1a;數據親和性&a…

youtube圖論

dfs排序lifo & fifo存儲方式鄰接矩陣dijstra處理過的保存/更新&#xff0c;意味著一個節點避免了重復訪問bfs dfs

借助ssh實現web服務的安全驗證

背景 公有云服務器 http 服務 80端口&#xff0c;想做到安全訪問無須HTTPS 客戶端證書方便、快捷、安全 SSH 隧道 本地代理 使用 SSH 隧道將 HTTP 服務“隱藏”在 SSH 之后&#xff1a; # 客戶端建立隧道&#xff08;將本地 8080 轉發到服務器的 80 端口&#xff09; ssh…

狀態機在前端開發中的藝術:從理論到框架級實踐

文章目錄一 狀態機&#xff1a;復雜邏輯的終結者1.1 什么是狀態機&#xff1f;1.2 為何前端需要狀態機&#xff1f;二 狀態機核心概念深度解析2.1 有限狀態機&#xff08;FSM&#xff09;與分層狀態機&#xff08;HSM&#xff09;2.2 狀態機的數學表示三 前端開發中的狀態機實戰…

把word中表格轉成excle文件

把word中表格轉成excle文件 from docx import Document from openpyxl import Workbook from pathlib import Path# 打開 Word 文檔 document Document(./weather_report.docx) tables document.tables# 輸出文件路徑 output_file Path(./weather_report.xlsx)# 如果文件已存…