【iOS】Block基礎知識和底層探索

文章目錄

  • 前言
  • Block的聲明和創建
  • 問題引入
  • Block的底層結構
  • Block的執行流程
    • Block的創建與存儲
    • Block的傳遞與調用
  • Block的捕獲機制
    • 捕獲局部變量
    • 捕獲全局變量
    • 小結
  • Block的類型
  • __block修飾符
  • __block變量的包裝結構體
    • block的實例結構體
    • block的執行邏輯
  • Block循環引用
    • 造成的原因
    • 解決方法
    • 小結
  • 總結

前言

??最近在復習OC知識,發現自己對于block的了解很少,很多東西當時都沒有搞明白或者甚至不知道,然后就對block進行了重新學習。

Block的聲明和創建

Block 的語法格式為:

返回值類型 (^Block名稱)(參數列表) = ^返回值類型(參數列表) { 代碼塊 };

示例:

// 聲明并創建一個無參數、無返回值的 Block
void (^myBlock)(void) = ^void(void) {NSLog(@"Block 執行");
};// 調用 Block
myBlock(); // 輸出:"Block 執行"

問題引入

由此,我們有以下代碼,后面所有的實例分析都是在此基礎上:

請添加圖片描述

運行結果:

請添加圖片描述

我們可以看到,上述代碼進行了兩次block捕獲,第一次捕獲A和B的初始值,A為3,B為7;然后在myblock函數外對A、B變量進行自增修改處理,再次調用myblock函數進行捕獲,變量A輸出不變為3,變量B輸出為自增后的值8,控制變量可以發現,造成這種結果,唯一不同的是我們在定義變量B時,在前面用了==__block==進行修飾。

由此我們產生了 一系列問題:block內部是什么結構?為什么在block外部修改變量不會影響到其在block內的捕獲?為什么聲明時用__block進行修飾后,外部修改變量block捕獲也會變?可不可以直接在block內部進行變量修改?

下面,我們來對這些問題進行一一探討。

Block的底層結構

我們用clang -rewrite-objc main.m -o main.cpp將上述代碼文件轉換成C++文件:

int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int A = 3;__attribute__((__blocks__(byref))) __Block_byref_B_0 B = {(void*)0,(__Block_byref_B_0 *)&B, 0, sizeof(__Block_byref_B_0), 7};void(*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, A, (__Block_byref_B_0 *)&B, 570425344));((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);A++;(B.__forwarding->B)++;z((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);}return 0;
}

我們可以看到,我們定義myblock函數的代碼在C++中是這樣的:

請添加圖片描述

上述代碼Block底層實現的編譯器生成代碼,代碼的核心是將一個 Block 的底層結構體指針轉換為函數指針,并賦值給 myblock變量,具體形式:

void(*myblock)(void) = ((void (*)())&__main_block_impl_0(...));

等號左邊等名義個函數指針myblock,類型為void(*)(void)(無參數、無返回值);

等號右邊通過調用__main_block_impl_0函數生成 Block 的底層結構體,并將其轉換為函數指針后賦值給 myblock。

__main_block_impl_0是編譯器自動生成的 Block 初始化函數,負責創建 Block 的底層結構體(如 struct __block_impl)并初始化其核心字段。其參數通常包括:

參數類型/含義
(void *)__main_block_func_0指向 Block 實際執行邏輯的函數指針(即 FuncPtr字段的內容)
&__main_block_desc_0_DATA指向 Block 描述符(struct __block_descriptor)的指針,包含 Block 的元數據(如大小、復制/銷毀函數)
A被 Block 捕獲的變量(如基本類型、不可變對象)
(__Block_byref_B_0 *)&B指向 __Block_byref_B_0結構體的指針,用于捕獲可變對象或需要引用捕獲的變量
570425344標志位(可能表示 Block 的行為選項,如是否復制捕獲變量、是否啟用優化等)

從剛剛我們在main.m的cpp文件里看到的定義函數,我們繼續探究找尋__main_block_impl_0函數的源碼定義:

請添加圖片描述

struct __main_block_impl_0包含以下核心成員:

成員類型/含義
implstruct __block_impl類型,Block 的核心實現結構體(封裝函數指針、類型標識、標志位等)。
Descstruct __main_block_desc_0*類型,指向 Block 描述符(存儲元數據,如復制/銷毀函數)。
Aint類型,捕獲的整型變量(值捕獲)。
B__Block_byref_B_0*類型,指向引用捕獲的可變對象的輔助結構體(引用捕獲)。
構造函數初始化各成員,綁定 Block 的執行邏輯、捕獲變量和元數據。

然后我們順藤摸瓜找到了Block 的核心實現結構體struct __block_impl函數的源碼:請添加圖片描述

由上述過程,我們可以知道,Block的底層本質是一個結構體,內部封裝了一些關鍵信息:

函數指針:指向Block對應的可執行代碼

捕獲的變量:Block執行時需要訪問的外部變量()

執行上下文:包括 Block 的引用計數、所屬的類(用于調試)等元數據

有一張圖非常好地說明了block的底層調用和邏輯:

請添加圖片描述

? ——圖片來自博客【iOS】Block底層分析@zhngxvy

Block的執行流程

Block 的執行分為??創建??、??存儲??、??傳遞??和??調用??四個階段,核心是??函數指針的調用??和??捕獲變量的管理??。

Block的創建與存儲

創建:通過 ^語法定義 Block 時,編譯器會生成一個 struct Block_layout結構體實例,并將代碼塊編譯為對應的機器指令(存儲在invoke函數指針中)。

存儲位置:Block 初始存儲在棧上(棧 Block),但以下場景會觸發 Block 被復制到堆上(堆 Block):

  • Block 被賦值給一個強引用的變量(如 __strong修飾的屬性或局部變量)。
  • Block 被作為參數傳遞給一個異步函數(如 dispatch_async)。
  • Block 被顯式復制(調用 Block_copy())。

Block的傳遞與調用

Block 可以像對象一樣被傳遞(如作為方法參數、存儲在集合中),其調用的本質是執行 invoke函數指針,并傳遞參數。

示例(Block 作為方法參數):

// 定義一個接受 Block 的方法
- (void)executeBlock:(void (^)(void))block {NSLog(@"準備執行 Block...");block(); // 調用 Block(執行 invoke 函數)NSLog(@"Block 執行完成");
}// 使用
[self executeBlock:^{NSLog(@"自定義 Block 邏輯");
}];

執行流程

  1. 定義 Block 時,編譯器生成棧上的 struct Block_layout
  2. 將 Block 作為參數傳遞給 executeBlock:方法時,Block 被復制到堆上(因方法參數需要強引用)。
  3. 方法內部調用 block()時,觸發invoke函數指針,執行 Block 的代碼邏輯。

Block的捕獲機制

Block 可以捕獲外部作用域的變量(如局部變量、實例變量),捕獲行為分為兩種:值捕獲和指針捕獲

捕獲局部變量

auto:自動變量,離開作用域就自動銷毀,只存在于局部變量(沒有特別關鍵字修飾,一般默認為auto)
static:靜態局部變量

我們先來看以下代碼:

#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {int A = 18;//局部變量(自動變量)static int B = 52;//靜態局部變量void(^jubuBlock)(void) = ^{NSLog(@"jubuBlock - A:%d - B:%d", A, B);};jubuBlock();NSLog(@"輸出1 - A:%d B:%d", A, B);A = 20;B = 100;jubuBlock();NSLog(@"輸出2 - A:%d B:%d", A, B);}return 0;
}

輸出結果如下:

請添加圖片描述

由此,我們可以發現,當我們在block函數外部修改局部變量時,block函數內部的自動變量不會受影響,而靜態局部變量會跟著被修改。這是為什么呢?

為了探尋這一原因,我們將上述代碼文件轉化為cpp文件,我們可以發現__main_block_impl_0結構體定義如下:

請添加圖片描述

我們可以得到以下結論:

  • A:捕獲的自動變量(按值復制)。
  • B:捕獲的靜態局部變量(按指針復制,存儲其內存地址)。

這就可以解釋剛剛的問題:為什么在block函數外部修改自動變量,內部的變量值不會產生改變,而修改靜態變量就會變?到這里,block捕獲局部變量的本質就很明顯了:

自動變量:按值捕獲(復制其當前值到block內部)。即使外部變量后續被修改,block內部使用的仍是捕獲時的副本。

靜態局部變量:按指針捕獲(存儲其內存地址)。外部變量修改時,block通過指針訪問的是最新值。

捕獲全局變量

首先,有以下代碼:

#import <Foundation/Foundation.h>int A = 18;//全局變量
static int B = 52;//靜態全局變量int main(int argc, const char * argv[]) {@autoreleasepool {void(^quanjuBlock)(void) = ^{NSLog(@"quanjuBlock - A:%d - B:%d", A, B);};quanjuBlock();NSLog(@"輸出1 - A:%d B:%d", A, B);A = 20;B = 100;quanjuBlock();NSLog(@"輸出2 - A:%d B:%d", A, B);}return 0;
}

輸出結果:請添加圖片描述

我們再來看這部分源碼:

請添加圖片描述

我們發現,這部分的源碼跟上面局部變量有點出入,在全局變量一直在__main_block_impl_0結構體定義中并沒有我們聲明的全局變量A和B,即全局、全局靜態變量并沒有出現在我們的Block實現結構體中,說明二者無法被捕獲。而全局變量存儲在內存中,打印的一直是最新的值。

小結

自動變量(局部變量)靜態局部變量全局變量
存儲區域棧(Stack)全局數據區(Data Segment)全局數據區(Data Segment)
生命周期隨作用域結束(如函數返回)銷毀程序啟動時創建,程序結束時銷毀程序啟動時創建,程序結束時銷毀
默認捕獲機制按值拷貝(復制當前值到block內部)按指針拷貝(存儲變量內存地址)按指針拷貝(存儲變量內存地址)
block內訪問形式直接使用拷貝后的副本(獨立于原變量)通過指針解引用訪問原變量通過指針解引用訪問原變量
外部修改的影響不影響block內部的副本(block捕獲的是歷史值)影響block內部(指針指向的原變量被修改)影響block內部(指針指向的原變量被修改)
是否支持修改捕獲值默認不可直接修改(需__block修飾)可直接修改(通過指針操作原變量)可直接修改(通過指針操作原變量)

產生這種差異的原因

auto和static:因為作用域的問題,自動變量的內存隨時可能被銷毀,所以要捕獲就趕緊把它的值拿進來,防止調用的時候訪問不到。

靜態局部變量:存儲在全局數據區,生命周期長于block,因此block捕獲其指針后,即使原變量所在作用域銷毀(如函數返回),仍可通過指針訪問最新值。

注意:盡管靜態局部變量的生命周期很長,但其**作用域(可訪問范圍)**僅限于聲明它的函數內部:

  • 函數外部無法直接訪問該變量(即使通過指針或引用)。
  • 不同函數中聲明的同名靜態局部變量相互獨立(因為各自存儲在全局數據區的不同位置)。

全局變量:在Block中訪問局部變量相當于是跨函數訪問,要先將變量存儲在Block里(捕獲),使用的時候再從Block中取出,而全局變量是直接訪問。

tips:

自動變量按值捕獲是安全的(避免懸垂指針),但可能增加內存拷貝開銷(大對象需注意)。

靜態/全局變量按指針捕獲更高效(無拷貝),但需注意多線程并發修改時的線程安全問題。

Block的類型

上述說到Block含有isa指針,而OC對象的isa指針指向它的類型,那么Block的類型都有哪些呢?

首先有以下代碼:

void (^block)(void) = ^{NSLog(@"hello world!");
};
NSLog(@"%@ %@", block, [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block  class] superclass] superclass]);

運行后結果如下:
請添加圖片描述
我們可以發現這個block的類型是NSGlobalBlock,其父類是NSBlock,根父類是NSObject,這也能說明Block是一個OC對象。

除了NSGlobalBlock,Block還有哪些類型,我們先來看如下代碼:

        void (^block1)(void) = ^{NSLog(@"hello world!");};NSLog(@"%@ %@", block1, [block1 class]);int A = 3;void (^block2)(void) = ^{NSLog(@"%d", A);};NSLog(@"%@ %@", block2, [block2 class]);NSString *string = @"hello world!";void (^block3)(void) = ^{NSString *stringCopy = [string copy];};NSLog(@"%@ %@", block3, [block3 class]);

當我們在ARC環境下運行代碼后,有如下結果:
請添加圖片描述

隨后,我們在項目的Bulid Settings中關閉ARC:

請添加圖片描述

隨即得到如下結果:

請添加圖片描述

我們可以發現,在MRC環境下,若block不捕獲任何外部變量,且未被復制,則其類型為__NSGlobalBlock__;若block捕獲外部變量,但未被復制,則其類型為__NSStackBlock__;若block被復制,則其類型為__NSMallocBlock__

但如果是在ARC環境下,即使不顯式調用copy也會是__NSMallocBlock__類型,這是因為在ARC下,編譯器會自動插入內存管理代碼(如retainreleasecopy),核心目的是確保block在需要跨作用域存活時保持有效,確保對象(包括block)在其生命周期內被正確管理

所以,我們可以做出如下總結:

存儲位置觸發條件生命周期
棧上block未被復制(僅在局部作用域使用,未被賦值給id類型變量或作為參數傳遞)離開作用域時自動銷毀(無法跨作用域使用)
堆上block被復制(顯式調用copy,或隱式被id類型變量持有、作為方法參數傳遞等)。由引用計數管理,直到release計數歸零后銷毀。
全局區block不捕獲任何外部變量,且未被復制。程序啟動到結束時一直存在(全局唯一)。

__block修飾符

在我們前面的學習中,我們知道在block函數外部修改自動變量,函數內部捕獲的值不會受到影響,那如果我們想修改在block中捕獲的自動了變量的值怎么辦?

首先,我們肯定想到說,想修改?那我直接在函數內部進行修改行不行!我們試一下:

請添加圖片描述

我們會發現,在block函數內部直接修改自動變量會發生報錯:Variable is not assignable (missing __block type specifier),翻譯過來就是:變量未分配(丟失 __block 類型限定符)。

這是因為自動變量的默認捕獲機制:在OC中,block對自動變量的默認捕獲方式是按值復制Copy by Value):

  • 當block定義時,會復制自動變量的當前值到block內部的副本中。
  • block內部訪問該變量時,實際訪問的是副本,而非原變量
  • 因此,若在block內部嘗試修改該變量(如賦值操作),編譯器會報錯:因為修改的是副本,原變量無法被block直接修改,這是不允許的。

那么我們真的沒有辦法修改自動變量了嗎😭有的兄弟有的!

如果我們需要在block內部修改自動變量,需用__block修飾符聲明該變量。好的,我們現在進行實際操作試試😋:

請添加圖片描述

OK!對自由變量A加了__block修飾后就可以在block函數內部進行修改了?

嘶~這個__block到底有什么魔力?下面我們來探索一下。

先說結論:__block的作用是將自動變量轉換為塊級變量(Block Variable),使其被block和原作用域共享同一實例。

__block變量的包裝結構體

現在我們來看看__block這部分的cpp源碼:

請添加圖片描述
我們發現,被__block修飾的自動變量A在源碼中,編譯器生成了一個包裝結構體__Block_byref_A_0,用于存儲被修飾變量的值及其元數據。

其成員含義:

成員類型說明
__isavoid*指向類對象的指針(通常為_NSConcreteStackBlock或堆上的類,初始可能為NULL)。
__forwarding__Block_byref_A_0*轉發指針,指向變量的實際存儲位置(棧或堆)。當block被拷貝到堆時,此指針會指向堆中的副本。
__flagsint標志位(如是否被拷貝、是否需要釋放等)。
__sizeint結構體的大小(用于內存管理)。
Aint被__block修飾的原始變量

通過此結構體,block和原作用域的變量共享同一內存空間,實現“跨作用域修改變量”的能力。

block的實例結構體

block的實例結構體( __main_block_impl_0)源碼如下:

請添加圖片描述

block的實例通過此結構體表示,包含block的執行邏輯、描述信息及對__block變量的引用:

結構體成員類型說明
implstruct __block_implblock的基類結構體,包含isa(類型標識)、Flags(標志位)、FuncPtr(執行函數指針)
Descstruct __main_block_desc_0*指向block描述符的指針,記錄block的內存布局和回調函數(如拷貝、銷毀)
A__Block_byref_A_0*指向__block變量包裝結構體的指針(通過引用捕獲,而非值拷貝)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_A_0 *_A, int flags=0) : A(_A->__forwarding) {  // 初始化時,A指向包裝結構體的轉發指針impl.isa = &_NSConcreteStackBlock;  // 標記為棧上blockimpl.Flags = flags;                 // 設置標志位(如是否需要拷貝)impl.FuncPtr = fp;                  // 綁定執行函數(如__main_block_func_0)Desc = desc;                        // 綁定描述符
}

A(_A->__forwarding)表示block實例的A成員直接指向__Block_byref_A_0結構體的__forwarding指針。這確保了無論block是否被拷貝到堆,A始終指向變量的實際存儲位置(棧或堆)。

block的執行邏輯

block的實際執行體(__main_block_func_0:),其源碼如下:

通過__cself(block實例自身)訪問捕獲的變量。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_A_0 *A = __cself->A;  // 獲取包裝結構體指針(A->__forwarding->A) = 19;  // 修改被包裝的變量A的值(通過轉發指針)NSLog((NSString *)&__NSConstantStringImpl__... , (A->__forwarding->A));  // 打印修改后的值
}

通過A->__forwarding->A訪問被__block修飾的變量A。__forwarding指針確保即使block被拷貝到堆,仍能正確訪問變量的實際存儲位置(棧或堆副本)。

Block循環引用

Block的本質是一個對象(繼承自NSObject),其內存管理遵循ARC規則。當Block捕獲外部對象(如self)時:

  • 默認情況下,Block會強引用捕獲的對象(對對象類型變量按引用捕獲,基本類型按值捕獲)。

  • 如果外部對象(如self)本身強引用該Block(例如將Block作為self的屬性),會形成強引用閉環:

    self → Block(強引用) → self(強引用)

    此時兩者的引用計數均無法歸零,導致內存泄漏。

造成的原因

Block作為對象的屬性,且內部捕獲self

當對象的屬性是Block,且Block內部訪問了self(如調用self的方法或屬性),同時對象強引用該Block時,形成循環引用。

// ViewController.h
@interface ViewController : UIViewController
@property (nonatomic, strong) void (^myBlock)(void); // Block屬性(強引用)
@end// ViewController.m
@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 1. 創建Block,內部捕獲self(強引用)self.myBlock = ^{ NSLog(@"Self: %@", self);  // 捕獲self(強引用)};// 2. self強引用myBlock(屬性是strong)// 此時形成循環引用:self → myBlock → self
}@end

循環路徑:

self(ViewController實例)通過strong屬性強引用myBlock→ myBlock通過默認的強引用捕獲self→ 兩者引用計數均無法歸零。

Block被嵌套對象持有,且捕獲外層self

當Block被另一個對象(如manager)持有,而self又持有該manager,同時Block內部捕獲self時,也可能形成循環。

// Manager.h
@interface Manager : NSObject
@property (nonatomic, copy) void (^block)(void);
@end// ViewController.m
@implementation ViewController {Manager *_manager;
}- (void)viewDidLoad {[super viewDidLoad];_manager = [[Manager alloc] init];// 1. Manager持有block(copy后為強引用)_manager.block = ^{ NSLog(@"ViewController: %@", self);  // Block捕獲self(強引用)};// 2. self持有_manager(強引用)// 循環路徑:self → _manager → block → self
}@end

解決方法

??解決循環引用的核心是打破強引用閉環,通常通過==弱引用(__weak__unsafe_unretained)==弱化Block對self的引用

使用__weak修飾self

在Block內部通過__weak修飾的weakSelf引用self,避免Block強引用self。

// ViewController.m
@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 1. 定義弱引用指向self(打破循環)__weak typeof(self) weakSelf = self;// 2. Block捕獲weakSelf(弱引用)self.myBlock = ^{ // 使用weakSelf替代self(可能為nil)NSLog(@"Self: %@", weakSelf); };// 3. self仍強引用myBlock,但myBlock弱引用self → 無閉環
}@end

__weak typeof(self) weakSelf = self創建了一個弱引用weakSelf,其引用計數不增加,Block捕獲weakSelf(弱引用),因此self的引用計數不會因Block的持有而增加。

循環路徑被打破:self → myBlock → weakSelf(弱引用,不增加計數)。

使用__unsafe_unretained(不推薦)

__unsafe_unretained__weak類似,但不會自動將失效的指針置為nil,可能導致野指針(訪問已釋放的對象)。

__unsafe_unretained typeof(self) unsafeSelf = self;
self.myBlock = ^{ NSLog(@"Self: %@", unsafeSelf);  // 若self已釋放,unsafeSelf可能為野指針
};

__unsafe_unretained僅在以下場景使用:目標對象生命周期明確短于Block(無需置nil)或無法使用__weak(如兼容舊版本iOS)。

在Block執行完畢前確保self存活

若Block需要長時間執行(如異步任務),可通過臨時強引用確保self在Block執行期間不被釋放,執行完畢后自動釋放。

__weak typeof(self) weakSelf = self;
self.myBlock = ^{__strong typeof(weakSelf) strongSelf = weakSelf;  // 臨時強引用if (strongSelf) {[strongSelf doSomething];  // 在Block執行期間,strongSelf保持self存活}
};  // strongSelf在此處銷毀,不影響self的引用計數

__strong typeof(weakSelf) strongSelf = weakSelf在Block內部創建一個臨時強引用strongSelf,若weakSelf未失效(self仍存活),strongSelf會強引用self,確保Block執行期間self不被釋放,Block執行完畢后,strongSelf銷毀,self的引用計數恢復正常。

小結

所以,我們可以總結:Block循環引用的核心是強引用閉環,解決方案的關鍵是弱化其中一個環節的引用。最常用的方法是使用__weak修飾self,在Block內部通過弱引用訪問self,打破循環。同時需注意:

  • 避免在Block內部直接強引用self(默認行為)。
  • 若需在Block執行期間確保self存活,可結合臨時強引用(__strong)。
  • __unsafe_unretained需謹慎使用,防止野指針崩潰。

總結

??Block是Objective-C中強大的閉包工具,在iOS開發中十分重要,常用于處理異步操作、回調、集合操作等,核心能力是捕獲并保存環境狀態,支持靈活的異步編程和回調邏輯。理解其存儲位置(棧/堆/全局)變量捕獲規則(值/指針/引用)內存管理(ARC/MRC)循環引用解決方案是掌握Block的關鍵。

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

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

相關文章

1.Ansible 自動化介紹

1-Ansible 自動化介紹 Ansible 自動化介紹 手動執行任務和自動化執行任務 手動執行任務的麻煩事&#xff1a; 很容易漏掉某個步驟&#xff0c;或者不小心執行錯步驟&#xff0c;而且很難驗證每個步驟是不是真的按預期完成了。管理一大堆服務器時&#xff0c;很容易出現配置…

2025年云手機場景適配的行業觀察

2025年的市場中&#xff0c;云手機品牌百花齊放&#xff0c;不同品牌在性能、功能和場景適配性上的差異日益顯著。隨著云計算技術的快速發展&#xff0c;云手機已從 嘗鮮工具 演變為游戲、辦公、企業運營等場景的剛需工具。現市面上也有著更多的云手機品牌&#xff0c;結合實測…

Date/Calendar/DateFormat/LocalDate

作用說明Date用于定義時間&#xff0c;提供date對象間的比較方法Calendar(日歷類),提供對時間的運算方法DateFormat是接口&#xff0c;它的實現類SimpleDateFormat用來規范時間輸出形式LocalDate&#xff0c;在JDK1.8之后引入&#xff0c;方便了對時間的運算方法介紹Date常用方…

在Python 3.8環境中安裝Python 3.6兼容包的方法

在Python 3.8環境中安裝Python 3.6兼容包的方法 用戶的需求是&#xff1a;在Python 3.8環境中重新安裝原本為Python 3.6設計的包。這通常涉及兼容性問題&#xff0c;因為Python 3.8可能引入了一些語法或API變更&#xff0c;導致舊包無法直接運行。以下是逐步解決方案&#xff…

三種DuckDB電子表格插件的union all查詢性能對比

我選取了最穩定、兼容性最好的三種&#xff1a;官方excel對應函數read_xlsx()、官方spatial對應函數st_read()、rusty_sheet對應函數read_sheet。 1.建立兩個包含前50萬和后54萬的xlsx文件&#xff0c;用于比較。利用官方excel的copy()to進行。 D copy (from v1 order by l_ord…

Python 中使用多進程編程的“三兩”問題

文章目錄一、簡介二、選擇合適的啟動方式三、手動終止所有的進程小結一、簡介 這里簡單介紹在Python中使用多進程編程的時候容易遇到的情況和解決辦法&#xff0c;有助于排查和規避某類問題&#xff0c;但是具體問題還是需要具體分析&#xff0c;后續會補充更多的內容。 二、…

Ansible部署應用

目錄Ansible概述1&#xff1a;什么是Ansible2&#xff1a;Ansible的架構組成3&#xff1a;Ansible與SaltStack的對比安裝部署Ansible服務1&#xff1a;系統環境設置2&#xff1a;安裝Ansible&#xff08;第一臺&#xff09;2&#xff1a;配置主機清單3&#xff1a;修改Ansible配…

疏老師-python訓練營-Day44預訓練模型

浙大疏錦行 知識點回顧&#xff1a; 預訓練的概念常見的分類預訓練模型圖像預訓練模型的發展史預訓練的策略預訓練代碼實戰&#xff1a;resnet18 作業&#xff1a; 嘗試在cifar10對比如下其他的預訓練模型&#xff0c;觀察差異&#xff0c;盡可能和他人選擇的不同嘗試通過ctrl進…

AI入門學習--如何寫好prompt?

寫好Prompt&#xff08;提示詞&#xff09;是駕馭AI模型的核心技能。以下是結合測試工程師需求的 結構化方法論 和 黃金模板一、prompt設計金字塔終極心法&#xff1a; Prompt 對AI的測試需求文檔&#xff0c;需像設計測試用例一樣&#xff1a;可執行&#xff1a;明確輸入輸出…

Linux編程 IO(標準io,文件io,目錄io)

標準IO C語言標準IO概述標準IO&#xff08;Standard Input/Output&#xff09;是C語言中用于處理文件和數據流的一組函數庫&#xff0c;定義在<stdio.h>頭文件中。與低級IO&#xff08;如read/write&#xff09;相比&#xff0c;標準IO提供了緩沖機制&#xff0c;提高了數…

C# WPF本地Deepseek部署

模型下載地址 using LLama; using LLama.Common; using System; using System.IO; using System.Threading.Tasks; using System.Windows; using System.Windows.Input;namespace YF_Talk {public partial class MainWindow : Window{private LLamaWeights _model;private LLa…

【Abp.VNext】Abp.Vnext框架模塊學習

1、Abp.Vnext-集成 Volo.Abp.Core2、Abp.vNext-Web模塊 Volo.Abp.AspNetCore.MVC框架&#xff08;framework文件夾&#xff09; 七、Abp.vNext-應用模塊-Identity身份認證 業務模塊&#xff08;modules文件夾->identity&#xff09; 1、添加領域模型 Volo.Abp.Identity.Doma…

【完整源碼+數據集+部署教程】火柴實例分割系統源碼和數據集:改進yolo11-rmt

背景意義 研究背景與意義 在計算機視覺領域&#xff0c;實例分割技術作為一種重要的圖像處理方法&#xff0c;近年來得到了廣泛的關注和應用。實例分割不僅能夠識別圖像中的物體類別&#xff0c;還能精確地分割出每個物體的輪廓&#xff0c;提供更為細致的視覺信息。這一技術在…

飛算JavaAI云原生實踐:基于Docker與K8s的自動化部署架構解析

一、飛算JavaAI詳細介紹 1.1 飛算JavaAI飛算JavaAI是飛算云智推出的一款革命性Java開發輔助工具&#xff0c;它通過人工智能技術深度賦能傳統軟件開發流程&#xff0c;特別為大學生課程設計、畢業設計等實踐教學環節提供了強有力的技術支持。在當前高校計算機相關專業教學中&am…

小程序打通美團核銷:解鎖到店綜合業態私域密碼,賦能6000+門店破局增長

數字化浪潮奔涌而來&#xff0c;棋牌室、臺球廳、親子樂園等線下綜合業態面臨經營轉型的關鍵節點。小程序與美團核銷功能的深度耦合&#xff0c;正成為撬動私域流量的核心杠桿&#xff0c;為超6000家門店打通了一條低成本、高轉化的經營快車道。過往經營模式中&#xff0c;線上…

Linux Shell:Nano 編輯器備忘

打開文件 sudo nano /etc/apt/sources.list選中多行&#xff0c;然后刪除 用方向鍵將光標定位到要刪除的起始位置按下 Alt A 設置錨點用方向鍵選擇要刪除的區域 (以上 3 步是為了選中文本)用 Ctrl K(剪切) 或 Alt D(直接刪除) 全選并刪除 按下 Alt \ 將光標移動到文件開頭…

常見的設計模式(2)單例模式

目錄 一、版本一&#xff1a;禁用構造與拷貝 二、版本二&#xff1a;注冊析構函數/嵌套垃圾回收 &#xff08;1&#xff09;使用atexit注冊程序結束時的函數 &#xff08;2&#xff09;使用對象嵌套垃圾回收 三、版本三&#xff1a;線程安全 四、版本四&#xff1a;編譯器…

JAiRouter 0.2.1 更新啦:內存優化 + 配置合并 + IP 限流增強,運維體驗再升級

JAiRouter 0.2.1 更新啦&#xff1a;內存優化 配置合并 IP 限流增強&#xff0c;運維體驗再升級 如果你已經在 0.2.0 生產環境中穩定運行&#xff0c;那么這篇更新會讓你無痛升級&#xff0c;直接“更輕、更穩、更省心”。 &#x1f4ce; 官方倉庫 & issue 直達 https://…

學習嵌入式第二十六天

文章目錄IO(續上)1.標準IO1.標準IO的接口2.流的定位2.文件IO1.概念&#xff1a;2.系統調用和庫函數3.文件IO函數接口習題IO(續上) 1.標準IO 1.標準IO的接口 fwrite 原型&#xff1a;size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); 功能&#xff1…

GDB 程序啟動參數設置深度指南

GDB 程序啟動參數設置深度指南 1. 概述 在程序調試過程中&#xff0c;正確設置啟動參數對于驗證程序行為、重現特定場景至關重要。GDB提供多種靈活的方式設置啟動參數&#xff0c;特別是當您需要調試命令行參數處理邏輯或配置敏感型應用時。 2. 參數設置的核心方法 2.1 啟動GDB…