Kotlin 協程:Channel 與 Flow 深度對比及 Channel 使用指南

在這里插入圖片描述

前言

在 Kotlin 協程的異步編程世界里,Channel 和 Flow 是處理數據流的重要工具,它們有著不同的設計理念與適用場景。本文將對比二者功能與應用場景,詳細講解 Channel 的使用步驟及注意事項 。

一、Channel 與 Flow 的特性對比

Channel 是協程間進行點對點通信的 “管道”,常用來解決經典的生產/消費問題。Channel 具備以下特效

  • 點對點通信:設計用于協程間直接數據傳遞,數據 “一對一” 消費,發送后僅能被一個接收方獲取 。
  • 生產者-消費者模式:典型的 “管道” 模型,生產者協程發數據,消費者協程收數據,適合任務拆分與協作(如多步驟數據處理,各步驟用協程 + Channel 銜接 )。
  • 即時性:數據發送后立即等待消費,強調 “實時” 通信,像事件驅動場景(按鈕點擊事件通過 Channel 傳遞給處理協程 )。
  • 背壓(Backpressure): Channel 內部通過同步機制處理生產消費速度差。發送快時,若緩沖區滿,發送端掛起;接收慢時,若緩沖區空,接收端掛起,自動平衡數據流轉 。

作為對比,再來看看 Flow 的特性

  • 數據流抽象:將異步數據視為 “流”,支持冷流(無訂閱不產生數據,如從數據庫查詢數據的 Flow ,訂閱時才執行查詢 )和熱流(如 SharedFlow,多訂閱者共享數據,數據產生與訂閱解耦 )。
  • 操作符豐富:提供 map(數據映射 )、filter(數據過濾 )、flatMapConcat(流拼接 )等操作,可靈活轉換、組合數據流,適合復雜數據處理場景(如網絡請求 + 本地緩存數據的流式整合 )。
  • 多訂閱者支持: SharedFlow 可廣播數據給多個訂閱者,數據 “一對多” 消費,如應用全局狀態變化(用戶登錄狀態),多個頁面協程訂閱 Flow 監聽更新 。
對比維度ChannelFlow
通信模式點對點,數據 “一對一” 消費支持 “一對多”(SharedFlow),數據可廣播
核心場景協程間任務協作、實時事件傳遞異步數據流處理、復雜數據轉換與多訂閱
背壓處理依賴 Channel 緩沖區與掛起機制通過操作符(如 buffer )或 Flow 自身設計處理
啟動特性無 “懶啟動”,發送數據邏輯主動執行冷流默認懶啟動,訂閱時才觸發數據生產
劃重點:推與拉的哲學

拋開 SharedFlow 這種一對多不談。Flow 也可以用作“一對一”通信,此時與 Channel 的主要區別是動作發起方不同:

  • Channel 是將數據從生產者推送給消費者,無論是否有接收方,發送數據的動作已經發生。

  • Flow(尤其是冷流)更像是一種拉取模型—— 收集器在收集數據時會“拉取”數據。如果沒有接收方請求,發起方不會生產數據。

很多人在面試中被問到兩者區別,回答了一堆技術細節,但是沒講到核心,理解“推與拉”的區別才是核心。

二、如何做技術選型

優先用 Channel 的場景
  • “一對一” 數據傳遞:網絡請求協程(發數據)與 UI 更新協程(收數據)通過 Channel 通信,確保數據有序更新界面 。

  • 串行異步任務:后臺任務拆分,多個協程分步處理數據(如 “讀取文件 → 解析 → 存儲”,每步用 Channel 銜接 )。

  • 事件驅動:處理實時、單次事件(如按鈕點擊、傳感器單次觸發 ),Channel 能保證事件 “即發即收”,不重復消費 。

優先用 Flow 的場景
  • 數據流處理:需對異步數據做復雜轉換(如網絡數據 + 本地緩存數據合并、過濾無效數據 ),Flow 的操作符可簡化邏輯 。
  • 多訂閱者共享數據:應用全局狀態(如用戶信息、主題配置 ),用 SharedFlow 廣播更新,多個協程訂閱同步狀態 。
  • 懶加載場景:數據生產耗時(如大文件讀取、復雜計算 ),Flow 的冷流特性可延遲執行,避免資源浪費 。

三、Channel 的基本使用步驟

  1. 創建 Channel:根據需求選擇 Channel 類型,如創建一個帶緩沖的 Channel:
val channel = Channel<Int>(capacity = 10) // 緩沖大小為 10 的 Channel,傳輸 Int 類型數據
  1. 發送數據(生產端):在協程中通過 send 方法發送數據:
CoroutineScope(Dispatchers.Default).launch {for (i in 1..10) {channel.send(i) // 向 Channel 發送 1 到 10 的整數}channel.close() // 數據發送完畢,關閉 Channel
}
  1. 接收數據(消費端):同樣在協程中通過 receive 或 consumeEach 等方式接收數據:
CoroutineScope(Dispatchers.Main).launch {channel.consumeEach { data ->Log.d("ChannelDemo", "接收數據:$data") // 消費 Channel 中的數據,這里打印數據}
}

四、四種不同構建方式

Kotlin 協程提供 4 種 Channel 類型,適配不同需求:

  • Rendezvous/無緩沖:默認值Channel()
  • Buffered/緩沖:Channel(capacity))
  • Conflated/合并:Channel(Channel.CONFLATED)
  • Unlimited/無限制:Channel(Channel.UNLIMITED)
Rendezvous Channel(默認類型)
  • 特性:無緩沖區,發送(send)和接收(receive)需 “同步碰頭” 。發送方先調用 send 會掛起,直到接收方調用 receive;反之亦然 。
  • 適用場景:嚴格同步的協程協作,如 “請求 - 響應” 模式(協程 A 發請求,協程 B 必須接收并響應后,A 才繼續執行 )。
val rendezvousChannel = Channel<String>()
// 發送協程
CoroutineScope(Dispatchers.IO).launch {rendezvousChannel.send("無緩沖數據") // 若此時無接收方,發送方會掛起
}
// 接收協程
CoroutineScope(Dispatchers.Main).launch {val data = rendezvousChannel.receive() // 接收數據,發送方恢復Log.d("ChannelDemo", "Rendezvous 接收:$data")
}
Buffered Channel
  • 特性:有固定大小緩沖區,發送方可連續發數據到緩沖區,直到填滿;緩沖區滿后,發送方掛起。接收方從緩沖區取數據,空了則掛起 。
  • 適用場景:平衡生產消費速度差,如日志收集(生產快,消費慢,緩沖區暫存日志 )。
Conflated Channel
  • 特性:緩沖區大小為 1,新數據覆蓋舊數據。發送方發數據時,若緩沖區有數據,直接替換;接收方始終取最新數據 。
  • 適用場景:關注 “最新狀態”,如實時傳感器數據(只需要當前最新值,舊值無意義 )。
val conflatedChannel = Channel<Int>(Channel.CONFLATED)
// 快速發送多條數據
CoroutineScope(Dispatchers.Default).launch {conflatedChannel.send(1)conflatedChannel.send(2)conflatedChannel.send(3) // 新數據會覆蓋舊數據,最終接收方拿到 3
}
// 接收協程
CoroutineScope(Dispatchers.Main).launch {val data = conflatedChannel.receive() Log.d("ChannelDemo", "Conflated 接收:$data") // 輸出 3
}
Unlimited Channel
  • 特性:緩沖區無界(理論上可存無限數據 ),發送方不會因緩沖區滿掛起,但需注意內存溢出風險(數據生產遠快于消費時,內存會持續增長 )。
  • 適用場景:數據量可控,或消費速度能追上生產速度(如固定任務隊列,任務數有限 )。實際項目中很少使用,因為經常會造成內存溢出。

五、Channel 實戰示例

示例1: 安卓 Snackbar 事件傳遞(協程間協作)

在安卓開發中,用 Channel 傳遞 “顯示 Snackbar” 事件:

  • 發送端:ViewModel 協程觸發事件,通過 Channel 發送消息 。
  • 接收端:Activity/Fragment 協程接收事件,更新 UI 顯示 Snackbar 。

優勢:解耦事件生產與消費,確保事件 “一對一” 處理,避免重復顯示 。

class SnackbarViewModel : ViewModel() {// 聲明 Channel,用于傳遞 Snackbar 消息(String 類型為例)private val _snackbarChannel = Channel<String>()// 暴露為 Flow,方便界面側收集(也可直接暴露 Channel,但 Flow 更符合 Jetpack 生態)val snackbarFlow = _snackbarChannel.receiveAsFlow()// 觸發 Snackbar 事件的方法(可在任意異步邏輯后調用)fun triggerSnackbar(message: String) {viewModelScope.launch {_snackbarChannel.send(message) // 發送事件到 Channel}}
}class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {val viewModel: SnackbarViewModel = viewModel()// 收集 Snackbar 事件流val snackbarMessage by viewModel.snackbarFlow.collectAsState(initial = "")Column {// 模擬觸發事件的按鈕Button(onClick = {viewModel.triggerSnackbar("操作成功!") // 觸發事件}) {Text(text = "顯示 Snackbar")}// 根據事件顯示 Snackbarif (snackbarMessage.isNotBlank()) {Snackbar(onDismiss = { /* 可在此處理 Snackbar 消失邏輯,比如置空消息 */ }) {Text(text = snackbarMessage)}}}}}
}
  • ViewModel 里用 Channel 作為 “事件管道”,發送端(triggerSnackbar)通過 send 傳遞消息。
  • 界面側通過 receiveAsFlow 將 Channel 轉為 Flow,用 collectAsState 收集狀態,驅動 UI 顯示 Snackbar。
  • 因 Channel 是 “一對一” 消費(receiveAsFlow 會按順序消費事件,且事件被消費后從管道移除 ),可避免重復顯示問題。
示例2: 多協程任務拆分(生產者 - 消費者)

處理 “讀取文件 → 解析 → 存儲” 流程:

  1. 協程 1(生產者):讀文件內容,發數據到 Channel 。
  2. 協程 2(消費者):從 Channel 取內容,解析后發新 Channel 。
  3. 協程 3(消費者):從新 Channel 取解析后數據,存入數據庫 。

優勢:拆分任務到不同協程,利用 Channel 串聯流程,實現并行處理(如讀文件和解析可部分并行 ),提升效率 。

假設的工具類(模擬文件讀取、數據庫存儲 )

object FileUtils {// 模擬 “讀取文件內容”,實際可替換為真實文件 IOsuspend fun readFileContent(filePath: String): String {delay(1000) // 模擬 IO 耗時return File(filePath).readText()}
}object DatabaseUtils {// 模擬 “插入數據庫”,實際可替換為 Room 等框架邏輯suspend fun insertIntoDb(data: String) {delay(500) // 模擬數據庫操作耗時println("已存入數據庫:$data") // 日志演示,實際可省略}
}

主邏輯代碼(協程拆分 + Channel 串聯 )

fun main() = runBlocking {// 1. 初始化 Channel://    - 第 1 個 Channel:傳遞原始文件內容(生產者 → 解析協程)val rawDataChannel = Channel<String>()//    - 第 2 個 Channel:傳遞解析后的數據(解析協程 → 存儲協程)val parsedDataChannel = Channel<String>()// 2. 啟動 3 個協程,模擬 “生產者 → 消費者 1 → 消費者 2” 流程val producerJob = launch(Dispatchers.IO) {// 生產者:讀文件(模擬)val content = FileUtils.readFileContent("/sdcard/sample.txt") rawDataChannel.send(content) // 發送原始內容到 ChannelrawDataChannel.close() // 發送完畢,關閉 Channel}val parserJob = launch(Dispatchers.Default) {// 消費者 1:解析數據for (rawData in rawDataChannel) { // 自動遍歷 Channel,直到關閉val parsedData = rawData.replace("\\s+".toRegex(), " ") // 簡單解析:去除多余空格parsedDataChannel.send(parsedData) // 發送解析后內容到下一個 Channel}parsedDataChannel.close() // 解析完畢,關閉 Channel}val storageJob = launch(Dispatchers.IO) {// 消費者 2:存儲到數據庫for (parsedData in parsedDataChannel) { // 自動遍歷 Channel,直到關閉DatabaseUtils.insertIntoDb(parsedData)}}// 3. 等待所有任務完成producerJob.join()parserJob.join()storageJob.join()println("所有流程執行完畢!")
}
  • 生產者協程(producerJob):負責 IO 操作(讀文件),將結果發送到 rawDataChannel。
  • 解析協程(parserJob):從 rawDataChannel 取數據、解析,再發送到 parsedDataChannel。
  • 存儲協程(storageJob):從 parsedDataChannel 取數據、執行數據庫插入。
  • 通過 Channel 串聯流程,讀文件和解析可并行(生產者讀文件時,解析協程可能已就緒等待數據 ),提升整體效率;同時代碼解耦,每個協程專注單一職責。

六、高級用法:扇入/扇出和雙向通信

扇入(Fan-In):

多個發送者,單個接收者。所有協程都對同一個實例調用 channel.send() 并由該單個接收者處理所有消息。這非常適合將來自多個生產者的數據聚合到一個消費者。

val channel = Channel<String>() // 多個生產者
repeat(3) { index -> launch { val producerName = "Producer-$index"repeat(5) { i -> channel.send("$producerName send item$i") } } 
} // 單個消費者
launch { repeat( 15 ) { val item = channel.receive() println( "Consumer received: $item " ) } channel.close() 
}
扇出 (Fan-Out):

單個發送者將數據發送給多個潛在消費者。注意:此時 多個接收者實際上會競爭消息。一個接收者消費的消息不會被另一個接收者看到,即一旦一個數據項被一個消費者讀取,它就消失了。如果你希望每個消費者都接收相同的數據,需要使用 SharedFlow

val channel = Channel< Int >() // 單個生產者
launch { repeat(10) { i -> channel.send(i) } channel.close() 
} // 多個消費者
repeat(2) { index -> launch { for (msg in channel) { println( "Receiver-$index receive $msg " ) } } 
}
雙向通信

由于 Channel 是單向的,因此有兩種主要方式來實現雙向通信:

方法1:使用兩個獨立的 Channel(最簡單的方法),一個 Channel 用于 A → B;另一個 Channel 為 B → A。

val channelAtoB = Channel<String>() 
val channelBtoA = Channel<String>() // 協程 A
launch { channelAtoB.send( " Hello from A !" ) val response = channelBtoA.receive() println( "A receive:$response " ) 
} // 協程 B
launch { val msg = channelAtoB.receive() println( "B receive:$msg " ) channelBtoA.send( "Hey A, this is B!" ) 
}

方法2:使用包含結構化消息的單一渠道

  • 定義一個密封類(或其他結構),表明誰發送了它或者它是什么類型的消息。
  • 兩個協程都從同一個 Channel 讀取,但只響應與它們相關的消息。
seal  class  ChatMessage { data  class  FromA ( val content: String) : ChatMessage() data  class  FromB ( val content: String) : ChatMessage() 
} val chatChannel = Channel<ChatMessage>() // 協程 A
launch { // 發送初始消息chatChannel.send(ChatMessage.FromA( "Hello from A" )) // 在同一 Channel 中等待 B 的響應for (msg in chatChannel) { when (msg) { is ChatMessage.FromB -> { println( "A got B's message: ${msg.content} " ) break} else -> { /* 忽略來自 A 自身的消息 */ } } } 
} // 協程 B
launch { for (msg in chatChannel) { when (msg) { is ChatMessage.FromA -> { println( "B got A's message: ${msg.content} " ) // 在同一 Channel 中響應chatChannel.send(ChatMessage.FromB( "Hey A, this is B!" )) break} else -> { /* 忽略來自 B 的消息 */ } } } chatChannel.close() 
}

方案2 有個風險:如果雙方同時等待發送和接收,且沒有任何額外的邏輯,則可能會陷入死鎖(兩個協程都暫停,等待對方讀取)。

方案1 兩個獨立 Channel 通常可以降低這種風險,因為雙方都可以發送消息,而無需等待對方從同一 Channel 消費,但是方案2會讓代碼變得復雜一些。方案各有利有弊,需要開發者自己權衡

七、Channel 異常處理

Channel 通信過程中很容易發生異常,妥善的異常處理非常重要。

使用 try-catch

發送或接收數據時可能出現異常,如 Channel 已關閉還嘗試發送。需用 try-catch 包裹關鍵操作:

一種直接的方法是將發送 / 接收操作包裹在 try-catch 塊中:

launch {try {channel.send("Important message")} catch (e: CancellationException) {// 協程被取消,按需處理或記錄日志} catch (e: Exception) {// 發送時出現的其他錯誤}
}

同樣的思路也適用于 receive() 調用:

launch {try {val msg = channel.receive()println("Received: $msg")} catch (e: ClosedReceiveChannelException) {// Channel 已關閉} catch (e: Exception) {// 處理其他異常}
}
使用 SupervisorJob

如果我們需要構建一個以協程為主的生產消費系統,可以將它們放在 SupervisorJob 或自定義的 CoroutineExceptionHandler 中,這樣可以確保一個失敗的協程不搞垮其他協程:

val supervisor = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + supervisor + CoroutineExceptionHandler { _, throwable ->// 記錄或處理未捕獲的異常
})// 然后在這個作用域中啟動生產者/消費者協程
出錯時及時 close

當 Channel 的某個階段出現錯誤時,需要注意關閉 Channel 以表示不會發送任何數據,也有助于通知其他協程停止等待更多數據。

例如:

launch {try {for (line in rawDataChannel) {val cleanedLine = transform(line)processedDataChannel.send(cleanedLine)}} catch (e: Exception) {// 記錄錯誤processedDataChannel.close(e) // 讓下游知道發生了故障} finally {processedDataChannel.close()}
}
ClosedSendChannelException

一個常見的錯誤是忽略這種情況:當發送方處于掛起狀態并等待發送時,Channel 可能會關閉。在這種情況下,Kotlin 會拋出 ClosedSendChannelException。我們可以在代碼中對這種情況妥善處理,例如重試或者加日志等。

launch {try {channel.send("Data that might fail if channel closes")} catch (e: ClosedSendChannelException) {// Channel 在掛起時被關閉// 決定如何處理或記錄這種情況}
}
重試或回退邏輯

有時在向 Channel 發送數據之前,需要重試失敗的操作(例如,網絡請求)。此時需要一個小循環:

suspend fun safeSendWithRetry(channel: SendChannel<String>, data: String, maxRetries: Int) {var attempts = 0while (attempts < maxRetries) {try {channel.send(data)return} catch (e: Exception) {attempts++if (attempts >= maxRetries) {throw e}delay(1000) // 重試前稍等片刻}}
}

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

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

相關文章

MYsql主從復制部署

MySQL 主從復制是將主數據庫的變更自動同步到從數據庫的過程&#xff0c;常用語讀寫分離、高可用性和數據備份。 1.環境準備 確保主從服務器已安裝相同版本的 MySQL&#xff0c;并能通過網絡互相訪問。 # 檢查 MySQL 版本 mysql -V 2.配置主服務器 &#xff08;1&#xff0…

安燈呼叫看板如何實現汽車生產異常秒級響應

在汽車零部件工廠的靜置車間&#xff0c;傳統生產管理依賴人工巡檢與紙質記錄&#xff0c;存在效率低、信息滯后、異常響應慢等問題。某汽車廠曾因物料靜置時間未及時監控&#xff0c;導致批次混料&#xff0c;損失超10萬元。而安燈呼叫看板系統的引入&#xff0c;通過實時狀態…

構造函數注入在spring boot 中怎么使用詳解

我們來詳細講解一下在 Spring Boot 中如何使用構造函數注入&#xff0c;并通過一個完整的、可運行的例子來演示。 構造函數注入是 Spring 官方最推薦的依賴注入方式&#xff0c;因為它能保證對象的不可變性和依賴的完整性。 核心理念 在 Spring Boot 中使用構造函數注入非常簡單…

2025.6.30-2025.7.06第26周:第一次參加頭馬演講俱樂部

現在是周一早上6:23&#xff0c;我開始寫上周的周總結。 3件超出預期的事 參加頭馬俱樂部絕對是最超出預期的&#xff0c;使得這個周末格外的快樂簡歷的第一版終于改完了&#xff0c;花了好長的時間&#xff0c;其中有一天心情還很蕩&#xff0c;因為&#xff0c;我想&#x…

2025使用VM虛擬機安裝配置Macos蘋果系統下Flutter開發環境保姆級教程--下篇

其實如何安裝VM,如何安裝MACOS網上的教程很多,我只是結合我的體驗重新整理了一次,接下來才進入本教程最核心的部分,Flutter開發環境的配置部分。、一.配置前準備 主要是準備相應的工具包,以及其他虛擬機設置1.工具包 工具包的版本也可以自行配置,我這主要是我使用的是F…

QSPI、OSPI與FSMC的區別與內存映射分析

QSPI、OSPI與FSMC的區別與內存映射分析 基本概念與區別 1. FSMC (靈活靜態存儲控制器) 接口類型&#xff1a;并行接口&#xff0c;通常8/16位數據總線總線標準&#xff1a;傳統并行總線協議速度&#xff1a;相對較低&#xff0c;通常最高約100MHz應用場景&#xff1a;SRAM、NOR…

系統思考與心智模式探索

成長的真正障礙&#xff0c;不是能力的不足&#xff0c;而是看待問題的局限。 在復雜多變的商業環境中&#xff0c;我們往往習慣于解決“眼前”的問題&#xff0c;卻忽視了深藏背后的系統性障礙。我們看到的只是表面的“癥狀”&#xff0c;而真正的根源&#xff0c;卻往往隱藏…

物聯網技術的關鍵技術與區塊鏈發展趨勢的深度融合分析

一、物聯網技術的核心架構與關鍵技術 物聯網技術體系由感知層、網絡層、平臺層、應用層和安全層構成&#xff0c;各層技術協同工作&#xff0c;實現物理世界與數字世界的深度融合。 感知層&#xff1a;物聯網的“感官” 傳感器技術&#xff1a;包括環境傳感器&#xff08;溫度…

針對Exhcnage Server的攻擊防范措施

一、背景介紹最近&#xff0c;安全研究人員揭露了一個名為 NightEagle&#xff08;又名 APT-Q-95&#xff09; 的高級持續性威脅&#xff08;APT&#xff09;組織。這個組織被觀察到利用 Microsoft Exchange 服務器中的零日漏洞鏈 進行攻擊&#xff0c;其主要目標是中國政府、國…

編程基礎:繼承

能幫到你的話&#xff0c;就給個贊吧 &#x1f618; 文章目錄繼承&#xff1a;使用基類成員&#xff1a;前提——派生類必須同樣支持基類成員(組件和功能)示例&#xff1a;動物Animal 有 鰓 和 會飛。則 鳥Bird 不是 Animal&#xff0c;因為Bird雖會飛&#xff0c;卻沒有鰓。魚…

TMC4361A 使用(未驗證)

prompt 我用STM32F103C8T6 來控制 TMC4361A 運動控制芯片 &#xff0c;我配置 STM32F103C8T6 的 SPI1 與 TMC4361A 進行通信&#xff0c;配置 PA4 作為片選線&#xff0c;配置 PA8 作為 RCC_MCO 輸入時鐘輸入到 TMC4361A, 并將其連接到TMC4361A的CLK_EXT引腳。我想控制 TMC4361…

深度剖析:如何解決Node.js中mysqld_stmt_execute參數錯誤

在Node.js后端開發中&#xff0c;使用mysql2等數據庫驅動與MySQL/MariaDB交互時&#xff0c;Incorrect arguments to mysqld_stmt_execute 是一個令人頭疼的錯誤。它通常意味著你傳遞給SQL預處理語句的參數數量與SQL字符串中問號&#xff08;?&#xff09;占位符的數量不匹配。…

Vue3 學習教程,從入門到精通,Vue 3 安裝指南及語法知識點詳解(2)

Vue 3 安裝指南及語法知識點詳解 本文將詳細介紹 Vue 3 的所有安裝方式&#xff0c;并深入講解 Vue 3 的語法知識點。此外&#xff0c;還將提供一些綜合性案例&#xff0c;展示如何綜合運用 Vue 3 的各項功能。一、安裝 Vue 3 的所有方式 Vue 3 提供了多種安裝方式&#xff0c;…

C++基礎復習筆記

一、數組定義 在C中&#xff0c;數組初始化有多種方式&#xff0c;以下是常見的幾種方法&#xff1a; 默認初始化 數組元素未顯式初始化時&#xff0c;內置類型&#xff08;如int、float&#xff09;的元素值未定義&#xff08;垃圾值&#xff09;&#xff0c;類類型調用默認構…

手機和PC遠控安全深度測評:TeamViewer/ToDesk/向日葵安全防線對比

聲明&#xff1a;本測試報告系作者基于個人興趣及使用場景開展的非專業測評&#xff0c;測試過程中所涉及的方法、數據及結論均為個人觀點&#xff0c;不代表任何官方立場或行業標準。 一、引言 當下遠程控制技術已深度融入大眾的工作與生活&#xff0c;無論是上班族在家操…

Windows 11的開始菜單調整為左下角布局

1.桌面右鍵個性化 2.個性化中任務欄 3.任務欄選擇任務欄行為 4.任務欄行為中 任務欄對齊方式選擇靠左即可

Go語言項目工程化 — 常見開發工具與 CI/CD 支持

在Go語言的項目工程化實踐中&#xff0c;常見開發工具與 CI/CD 支持是保障團隊協作、高效交付與項目質量的關鍵。以下是第 68 章的詳細內容。一、開發輔助工具Go語言生態為開發者提供了豐富的工具&#xff0c;以提高代碼質量與開發效率。1. 格式化與靜態檢查工具說明gofmt標準格…

OpenCV人臉分析------繪制面部關鍵點函數drawFacemarks()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 該函數用于在圖像上繪制面部關鍵點&#xff08;facial landmarks&#xff09;&#xff0c;例如使用 FacemarkLBF, FacemarkKazemi 等算法檢測到的…

Linux內核ext4 extent:解決大文件存儲難題的關鍵

在Linux 操作系統的龐大生態中&#xff0c;文件系統猶如一座城市的基礎設施&#xff0c;支撐著數據的有序存儲與高效訪問。而 ext4 文件系統&#xff0c;作為 Linux 文件系統家族中的重要一員&#xff0c;自誕生起便憑借諸多先進特性備受矚目。其中&#xff0c;extent 機制堪稱…

reactnative頁面適配UI設計尺寸px轉dp的完美解決方案px2dp精要篇

你的 px2dp 函數基本思路是正確的&#xff0c;但可以進一步優化以確保更精確的適配。以下是改進后的完美精確方案&#xff1a; 完美精確的適配方案 import { Dimensions, PixelRatio, Platform, ScaledSize } from react-native;// 獲取屏幕尺寸&#xff08;考慮橫豎屏&#…