kotlin Flow快速學習2025

其實,第一章節,只是讓你了解下Flow的基本情況。我們開發中,基本很少使用這種模式。所以來講,我們甚至可以直接使用StateFlow和SharedFlow才是正途。這是很多教程沒有說明的點。所以第一章隨便瀏覽下即可。日后再補充理解都是可以的。

1. 基本接觸Flow

//第一步:定義flow{}
class FlowStudyViewModel : ViewModel() {val timeFlow = flow {logt { "flow start..." }var time = 0while (true) {logt { "flow begin..." }emit("" + time++)Thread.sleep(3*1000) //模擬耗時操作logt { "flow end..." }}}
}//第二步:訂閱收集數據和顯示。collect是掛起函數。
lifecycleScope.launch {viewModel.timeFlow.collect{ time->logt { "flow get data " }showInfoTv.text = time}
}

學習kotlin的協程,flow等知識點,我一直很關心它的運行線程在哪里,這樣能更深入的理解它的設計理念和我們UI更新的方式。

上述代碼,是最簡單的使用,但是是存在問題的。界面會卡死。因為flow沒有指定運行線程。它是跑在主線程里面。因此,我們加上:

flow{//...
}.flowOn(Dispatchers.IO) //指定運行線程

緊接著,我們嘗試修改:

//第二步:訂閱收集數據和顯示。collect是掛起函數。
lifecycleScope.launch {viewModel.timeFlow.collect{ time->delay(2000) //add:接收的處理效率調整logt { "flow get data " }showInfoTv.text = time}
}

我們增加一句,影響收集處的執行間隔。最后就會得到如下日志:

SubThread[84]: flow begin...18
MainThread: flow get data 12
SubThread[84]: flow end...
SubThread[84]: flow begin...19
MainThread: flow get data 13
SubThread[84]: flow end...
SubThread[84]: flow begin...20
SubThread[84]: flow end...
MainThread: flow get data 14
MainThread: flow get data 15
MainThread: flow get data 16

就是數據會快,已經執行完畢,而收集處的處理慢慢繼續執行。

如果我們不想要中間的執行結果,就使用collectLatest:

viewModel.timeFlow.collectLatest{ time->  //替代collect

collect 對上游數據流流發出的每個值都收集并完整執行 lambda 函數的操作,而 collectLatest 如果在前一個值收集并執行 lambda 完成之前發出新值,則取消任何正在進行的收集和 lambda 函數。

稍微再講講這句話的意思:

比如:

lifecycleScope.launch {viewModel.timeFlow.collectLatest{ time->logt { "flow get data $time" }showInfoTv.text = timedelay(5000) //在操作前與后}
}flow {while (totalCount++ <= 20) {logt { "flow begin...$time" }emit("" + time++)delay(1000)logt { "flow end..." }}
}

由于收集處速度執行不夠快,新的數據又來了,如果是collect函數,則會慢慢完整執行整個collect的函數。而collectLatest則會取消后續的操作。兩種不同的設計,注意區別。

本章知識點小結:

  • collect是掛起函數,需要scope launch執行;
  • flow{}定義的是冷流,必須主動collect才會執行;emit發射數據;
  • flow{}的運行線程通過flowOn修改。查看flowOn的注釋,有介紹其他操作符分割后的執行區別和連續調用的第一個優先級。
  • collect vs collectLatest : 數據流的執行時間和收集函數體的執行取消與否。

取消

就是kotlin的掛起函數,去掉collect的Job即可。或者有生命周期的Scope限定自行取消。

2. 常用操作符

rxjava,或者,java Collection stream用過的,對于map,filter等操作符肯定不陌生。

flowOn

前面已經接觸過了,用來切換線程選擇。

map/filter/onEach

val timeFlow = flow {var time = 0var totalCount = 0while (totalCount++ <= 20) {logt { "flow begin...$time" }emit( time++)delay(1000)}
}.map { time->logt { "map begin: $time" }"time: $time"
}.flowOn(Dispatchers.IO)

map: 將第一段執行的emit int型,轉成了String型。

.filter { time->time % 3 == 0
}

filter: lamda返回boolean值來進行數據流過濾。

onEach: 遍歷。略。

catch

catch 操作符可以捕獲來自上游的異常。

flow {emit(1)throw RuntimeException()
}
.onCompletion { cause ->if (cause != null)println("Flow completed exceptionally")elseprintln("Done")
}
.catch{ println("catch exception") }
.collect{ println(it) }//結果:
1
Flow completed exceptionally
catch exception//onCompletion、catch 交換一下位置:
flow {emit(1)throw RuntimeException()
}
.catch{ println("catch exception") }
.onCompletion { cause ->if (cause != null)println("Flow completed exceptionally")elseprintln("Done")
}
.collect { println(it) }//執行結果
1
catch exception
Done

debounce

防抖,目前還是實驗性質。

主要使用場景,就是輸入框,監聽文本變化,然后抉擇是不是應該馬上使用呢?

以前類似這種代碼都見過吧:

handler.remove(mRunnable)
handler.postDelay(mRunnable, 1000)

在Flow的流式編程方式下,使用dobounce,來幫你過濾。

在給定的timeout間隔過濾最新的值。最新的值始終會被發送:
Example:
flow {emit(1)delay(90)emit(2)delay(90)emit(3)delay(1010)emit(4)delay(1010)emit(5)
}.debounce(1000)
得到結果:
3, 4, 5
注意:如果原流發射數據快于timeout才能被發射。

比如3這里,發射以后,時間超過了timeout才能被發送出去,如果下一次emit來的太快就被過濾掉了。

sample

.sample(1000)

采樣,傳入timeout值即可。隔這么久得到一次數據。

take

僅收集流中前 N 個元素,后續元素會被忽略。

flowOf(1, 2, 3, 4, 5).take(3) // 僅收集前 3 個元素(1, 2, 3).collect { println(it) }// 輸出:1 → 2 → 3

終止符

collect/collectLatest

略。最常用的。

reduce

與collect類似,都是掛起函數。這些操作符是用來終結flow流程的。

  • 求和、求積、字符串拼接等累積操作。
  • 需要將流數據轉換為單一聚合值。
val sum = flowOf(1, 2, 3, 4, 5).reduce { acc, value -> acc + value }println(sum) // 輸出:15(1+2+3+4+5)val result = flow {for (i in ('A'..'Z')) {emit(i.toString())}
}.fold("test: ") { acc, value -> acc + value}
println(result) //輸出:test: ABCDEFG...Z
fold

比reduce多了一個默認入參。reduce和fold,在實際使用較少,有類似場景回頭學習。

多流操作符

我們前面介紹的操作符,都是單獨一個Flow,這一小節介紹,多個Flow操作。用法和難度都比較復雜。本章節對于剛學Flow的童鞋,建議跳過,簡略瀏覽本章節,了解一下它們的作用,以后再學習。

flatMapConcat
sendGetTokenRequest().flatMapConcat { token ->sendGetUserInfoRequest(token)}.flowOn(Dispatchers.IO).collect { userInfo ->println(userInfo)}flow1.flatMapConcat { flow2 }.flatMapConcat { flow3 }.flatMapConcat { flow4 }.collect { userInfo ->println(userInfo)}

如上述代碼,遇到嵌套請求的問題,比如我們去拿用戶信息的時候,需要先請求Token數據,再拿Token去請求用戶信息,這里使用flatMapConcat比較合適。保證emit結果順序

flatMapMerge

與flatMapConcat不同點是不保證順序。

flatMapLatest

與flatMapMerge/flatMapContact,和collectLatest類似,如果來不及處理就將會丟棄。

flow {emit(1)delay(150)emit(2)delay(50)emit(3)
}.flatMapLatest {flow {delay(100)emit("$it")}
}.collect {println(it)}
打印:  
1
3
zip

flatMap是串行的。zip是并行的。

n sendRealtimeWeatherRequest(): Flow<String> = flow {// send request to realtime weatheremit(realtimeWeather)
}fun sendSevenDaysWeatherRequest(): Flow<String> = flow {// send request to get 7 days weatheremit(sevenDaysWeather)
}fun sendWeatherBackgroundImageRequest(): Flow<String> = flow {// send request to get weather background imageemit(weatherBackgroundImage)
}fun main() {runBlocking {sendRealtimeWeatherRequest().zip(sendSevenDaysWeatherRequest()) { realtimeWeather, sevenDaysWeather ->weather.realtimeWeather = realtimeWeatherweather.sevenDaysWeather = sevenDaysWeatherweather}.zip(sendWeatherBackgroundImageRequest()) { weather, weatherBackgroundImage ->weather.weatherBackgroundImage = weatherBackgroundImageweather}.collect { weather ->}}
}
combine

Flow 操作符 zip 和 combine 都是 Flow 的擴展函數,是用來實現合并流的操作符(組合操作符),都可以將兩個流合并為一個流

zip 用于將兩個流中的元素合并在一起,當兩個流中都有元素就會將這些元素組成一個新元素發出,如果任何一個流沒有元素,就不會發出任何元素,如果其中一個流發送的元素比另一個慢,就會等待另一個流發送元素,意味著合并元素操作是同步的

combine 用于將兩個流中的最新元素組合在一起,至少有一個流有新的元素的話,組合操作就會繼續進行下去,如果其中一個流發送的元素比另一個慢,就會使用最新的元素。

  • zip 操作符:同時進行兩個網絡 Api 接口的調用,并在兩個接口都調用完成后一起給出結果(實現在一個回調中返回兩個網絡請求的結果)進行下一步處理。zip 可以將兩個流中的元素成對進行合并處理,適用于兩個流長度相等或者只關心較短長度的流的元素的場景,先發射的元素會等待對應順序的后發射的元素到來后才進行合并操作。
  • combine 操作符:同時監聽多個輸入字段的狀態(比如用戶名、密碼),只有當所有的輸入都滿足條件時才啟用登錄提交的按鈕。combine 可以將兩個流中的最新的元素組合在一起,適用于需要基于多個流的最新狀態進行處理的場景,只要任意一個流發出新元素就會觸發組合操作。
buffer

buffer函數其實就是一種背壓的處理策略,它提供了一份緩存區,當Flow數據流速不均勻的時候,使用這份緩存區來保證程序運行效率。

flow函數只管發送自己的數據,它不需要關心數據有沒有被處理,反正都緩存在buffer當中。

而collect函數只需要一直從buffer中獲取數據來進行處理就可以了。

但是,如果流速不均勻問題持續放大,緩存區的內容越來越多時又該怎么辦呢?

這個時候,我們又需要引入一種新的策略了,來適當地丟棄一些數據。

那么進入到本篇文章的最后一個操作符函數:conflate。

conflate

buffer函數最大的問題在于,不管怎樣調整它緩沖區的大小(buffer函數可以通過傳入參數來指定緩沖區大小),都無法完全地保障程度的運行效果。究其原因,主要還是因為buffer函數不會丟棄數據。

而在某些場景下,我們可能并不需要保留所有的數據。

比如拿股票軟件舉例,服務器端會將股票價格的實時數據源源不斷地發送到客戶端這邊,而客戶端這邊只需要永遠顯示最新的股票價格即可,將過時的數據展示給用戶是沒有意義的。

因此,這種場景下使用buffer函數來提升運行效率就完全不合理,它會緩存太多完全沒有必要保留的數據。

那么針對這種場景,其中一個可選的方案就是借助我們在上篇文章中學習的collectLatest函數。

而collectLatest會當有新數據到來時而前一個數據還沒有處理完,則會將前一個數據剩余的處理邏輯全部取消,可能不是我們預期的結果。

而使用conflate有些數據則被完全丟棄掉了。因為當上一條數據處理完時,又有更新的數據發送過來了,那么這些過期的數據就會被直接舍棄。

3. 生命周期

3.1 自行把控生命周期

val timeFlow = flow {var time = 0while (true) {logd{"emit $time"}emit(time)delay(1000)time++}}//顯然有問題:home退到后臺,仍然在跑,持續打印日志emit和collect
lifecycleScope.launch {viewModel.timeFlow.collect { d->logd { "flow collect $d" }showInfoTv.text = "flow collect: $d"}
}//存在泄露風險:雖然退到后臺不工作了,暫時達到了效果。但是正如該函數被deprecated一樣,是存在泄露風險的。僅僅是暫停了該塊的執行。
lifecycleScope.launchWhenStarted {viewModel.timeFlow.collect { d->logd { "flow collect $d" }showInfoTv.text = "flow collect: $d"}
}//good:這樣達到的效果是每次home后,徹底取消了。回來重新執行。得到的結果也是從0開始。
lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.timeFlow.collect { d->showInfoTv.text = "flow collect: $d"}}
}

如果你需要數據被暫存,應該把time,放到viewModel里面,變成全局變量。

我的理解:flow的執行是沒有生命周期感知的。需要你自行把握collect掛起函數的執行生命周期。 推薦采用如下方式來保證執行的安全性:

lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.timeFlow.collect {//...}}
}

3.2 錯誤使用

collect是阻塞的。有多個執行需要launch分開。

//error:多個Flow不能放到一個lifecycleScope.launch里去collect{},因為進入collect{}相當于一個死循環,下一行代碼永遠不會執行
lifecycleScope.launch {flow1.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect {}flow2.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect {}
}//success: 正確寫法。如果就想寫到一個lifecycleScope.launch{}里去,可以在內部再開啟launch{}子協程去執行。
lifecycleScope.launch {launch {flow1.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect {}}launch {flow2.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect {}}
}

4. StateFlow&SharedFlow

之前學習都是冷流。現在來了解熱流。

隨著響應式編程在 Android 開發中的普及,開發者們開始尋求更有效的方式來處理狀態變化和事件通信。這正是 StateFlowSharedFlow 應運而生的背景。

StateFlow 是一個熱流(hot flow),它始終持有一個值并在值發生變化時發出更新。這使得 StateFlow 非常適合于表示可以隨時間變化的狀態,如 UI 控件的可見性或網絡狀態的變化。

與之相對,SharedFlow 是設計用來傳遞事件的。它可以發出獨立的、不連續的數據或事件,使其成為處理用戶交互、網絡響應或其他一次性事件的理想選擇。

4.1 StateFlow

StateFlow 是一個狀態容器,旨在以響應式的方式共享一個可變的數據狀態。它是一個熱流,意味著即使沒有調用(collect函數)也會保持其狀態。這種特性使得 StateFlow 適合于應用中需要觀察和響應狀態變化的場景。

重點就是替代LiveData

下面的代碼將作為最佳實踐模板代碼來編寫。帶狀態的數據Bean,通知和監聽處理。

sealed class DataState<out T> { //注意out重點object Loading : DataState<Nothing>()data class Success<out T>(val data: T) : DataState<T>()data class Error(val message: String?) : DataState<Nothing>()
}class FlowStudyViewModel : ViewModel() {private val _dataState = MutableStateFlow<DataState<String>>(DataState.Loading)val dataState: StateFlow<DataState<String>> = _dataState.asStateFlow()fun startLoad() {viewModelScope.launch {
//            _dataState.updateAndGet { //可以比較舊值一致就不通知出去
//                try {
//                    val data = api.request()
//                    DataState.Success(data)
//                } catch (e: Exception) {
//                    DataState.Error(e.message)
//                }
//            }_dataState.value = try {val data = api.request()DataState.Success(data)} catch (e: Exception) {DataState.Error(e.message)}}}
}//activity
lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.dataState.collect { d->val data:Stringwhen (d) {is DataState.Success -> {data = d.data.toString()}is DataState.Error -> {data = "error"ToastUtil.toastOnTop("${d.message}")}is DataState.Loading -> {data = "loading"}}showInfoTv.text = data}}
}

從寫法和實際的結果都與LiveData一樣。并且理解StateFlow僅僅當做一個狀態容器,旨在以響應式的方式共享一個可變的數據狀態。白話來講,相當于持有數據bean的一個可被監聽的數據,與LiveData的作用完全一致。

4.2 SharedFlow

SharedFlow 是另一種類型的 Kotlin Flow,專門用于事件的傳遞。與 StateFlow 不同,SharedFlow 不保持狀態,而是用于發送一次性或離散的事件。這使得 SharedFlow 成為管理事件通信的理想選擇,尤其是在需要處理多個觀察者的場景中。適用于:

  • 用戶交互事件,如按鈕點擊或滾動事件。
  • 系統通知,如網絡狀態變更或數據庫更新。
對于 SharedFlow 的使用,以下是一些高級技巧和最佳實踐:
事件去重:通過 distinctUntilChanged 或自定義邏輯確保事件不被重復處理。
事件緩沖:適當配置 SharedFlow 的緩沖區大小和回放策略,以處理高頻事件或防止事件丟失。
生命周期感知:在 Android 應用中,確保事件流的收集與組件的生命周期保持一致,避免內存泄漏。

代碼示例:

private val _eventFlow = MutableSharedFlow<MyEvent>()
val eventFlow: SharedFlow<MyEvent> = _eventFlow.asSharedFlow()fun sendEvent(event: MyEvent) {viewModelScope.launch {_eventFlow.emit(event)}
}

可以看到,它沒有初始化數據。

額外參數可選。

    public fun <T> MutableSharedFlow(// 每個新的訂閱者訂閱時收到的回放的數目,默認0replay: Int = 0,// 除了replay數目之外,緩存的容量,默認0extraBufferCapacity: Int = 0,// 緩存區溢出時的策略,默認為掛起。只有當至少有一個訂閱者時,onBufferOverflow才會生效。當無訂閱者時,只有最近replay數目的值會保存,并且onBufferOverflow無效。onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND)

StateFlow vs LiveData

引用前面說的,寫法和實際的結果都與LiveData一樣。并且理解StateFlow僅僅當做一個狀態容器,旨在以響應式的方式共享一個可變的數據狀態。白話來講,相當于持有數據bean的一個可被監聽的數據,與LiveData的作用完全一致。

區別點:

  • StateFlow 需要將初始狀態傳遞給構造函數,而 LiveData 不需要。

  • 當 View 進入 STOPPED 狀態時,LiveData.observe() 會自動取消注冊使用方,而從 StateFlow 或任何其他數據流收集數據的操作并不會自動停止。如需實現相同的行為,您需要從 Lifecycle.repeatOnLifecycle 塊收集數據流。

4.3 StateFlow vs SharedFlow

理解 StateFlowSharedFlow 之間的關鍵差異有助于你在合適的場景中做出正確的選擇。

  • 關鍵差異

狀態保持 vs 事件傳遞StateFlow 用于表示隨時間變化的狀態,而 SharedFlow 更適合于處理一次性或離散的事件。

單一訂閱者 vs 多訂閱者StateFlow 通常用于單一訂閱者模式,而 SharedFlow 能夠同時向多個收集器廣播事件。

初始化值StateFlow 要求有初始值,而 SharedFlow 沒有初始化值。即,StateFlow是粘性的,一注冊就會回調,類似LiveData。

何時選擇使用哪一個

  • 當需要表示并觀察某個隨時間變化的狀態時,選擇 StateFlow
  • 當需要處理用戶交互或系統事件,并且這些事件可能有多個觀察者時,選擇 SharedFlow

結合使用的策略

在一些復雜的場景中,StateFlowSharedFlow 可以組合使用。例如,你可以使用 StateFlow 來維護應用的狀態,同時使用 SharedFlow 來處理和響應用戶事件或系統通知。

示例1:

考慮一個簡單的聊天應用,其中包含消息的發送和接收功能。可以使用 StateFlow 來表示當前的消息列表,而使用 SharedFlow 來處理新消息的接收。

示例2:

社交媒體應用,需要處理用戶的點贊和評論事件。使用 SharedFlow,可以有效地廣播這些事件到應用的不同部分,如更新 UI 或發送網絡請求。

其他

MVI和MVVM

  • ?MVI?:
    嚴格遵循?單向數據流?(View → Intent → ViewModel → State → View)?13。用戶交互被封裝為Intent事件,觸發狀態更新后驅動UI渲染,確保狀態變化可預測。

  • ?MVVM?:
    通過數據綁定實現?雙向數據流?(View ? ViewModel ? Model)?12。View與ViewModel直接交互,靈活性高但可能增加數據不一致的風險。

callbackFlow

把基于回調的 API 轉換為數據流。

callbackFlow 是冷流,沒有接收者,不會產生數據。

callbackFlow:底層使用channel來進行中轉,首先通過produce創建一個ReceiveChannel。然后在調用collect的時候,在將channel的值取出來emit出去。

在產生數據的時候,有兩個方法可用:sendoffer

  • send : 必須在協程體內使用
  • trySend(offer的替代者) : 可以在非協程體內使用

舉例:

/*** 模擬網絡請求*/
fun requestApi(callback: (Int) -> Unit) {thread {Thread.sleep(3000)callback(3)}
}

改成callbackFlow:

注意里面的4要素。

val callbackFlow = callbackFlow { //1. 定義//模擬網絡請求回調try {requestCallbackFlowApi { result ->//發送數據trySend(result).isSuccess //2. 使用trySend在非協程調用發送}} catch (e: Exception) {close(e) //4. 自行關閉flow的方式,否則,flow不會主動關閉的。}awaitClose { //3. 必須有awaitClose的設定,否則程序報錯。//do something.}
}val job = GlobalScope.launch {flow.collect {//接收結果}
}

channelFlow

https://www.imooc.com/article/300248 有介紹channelFlow。我認為作為了解即可。它還不如callbackFlow常用。

flow 是 Cold Stream。在沒有切換線程的情況下,生產者和消費者是同步非阻塞的。
channel 是 Hot Stream。而 channelFlow 實現了生產者和消費者異步非阻塞模型。

Channel

https://blog.csdn.net/vitaviva/article/details/149141984

  • Channel特性

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

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

  • Flow 的特性

數據流抽象:將異步數據視為 “流”,支持冷流(無訂閱不產生數據,如從數據庫查詢數據的 Flow ,訂閱時才執行查詢 )和熱流(如 SharedFlow,多訂閱者共享數據,數據產生與訂閱解耦 )。
操作符豐富:提供 map(數據映射 )、filter(數據過濾 )、flatMapConcat(流拼接 )等操作,可靈活轉換、組合數據流,適合復雜數據處理場景(如網絡請求 + 本地緩存數據的流式整合 )。
多訂閱者支持: SharedFlow 可廣播數據給多個訂閱者,數據 “一對多” 消費,如應用全局狀態變化(用戶登錄狀態),多個頁面協程訂閱 Flow 監聽更新 。

對比維度ChannelFlow
通信模式點對點,數據 “一對一” 消費支持 “一對多”(SharedFlow),數據可廣播
核心場景協程間任務協作、實時事件傳遞異步數據流處理、復雜數據轉換與多訂閱
背壓處理依賴 Channel 緩沖區與掛起機制通過操作符(如 buffer )或 Flow 自身設計處理
啟動特性無 “懶啟動”,發送數據邏輯主動執行。冷流默認懶啟動,訂閱時才觸發數據生產。

用法略。自行學習。

引用:
https://juejin.cn/post/7390936500115095561

https://baijiahao.baidu.com/s?id=1784258499249583415&wfr=spider&for=pc

https://developer.android.google.cn/kotlin/flow?hl=zh-cn

https://www.imooc.com/article/300248

https://blog.csdn.net/qq36246172/article/details/141271993

https://juejin.cn/post/7383086531043590185?from=search-suggest

https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650270522&idx=1&sn=b05ff0909b3454cfb5e17bbfd4a37f60&chksm=88631a55bf149343b7bcfefa54b563df66a3b5fb7848c296dc50b964a3e06770241c6aa42a15&scene=21#wechat_redirect

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

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

相關文章

【人工智能99問】什么是教師強制?(16/99)

文章目錄什么是教師強制&#xff1f;教師強制&#xff08;Teacher Forcing&#xff09;的定義原比例&#xff08;Original Proportion&#xff09;教師強制的舉例說明&#xff08;一&#xff09;教師強制的舉例說明&#xff08;二&#xff09;優點和缺點解決曝光偏差的方法什么…

【WPF】WPF 自定義控件之依賴屬性

&#x1f4e6; WPF 自定義控件之依賴屬性 在開發 WPF 應用時&#xff0c;自定義控件能幫助我們復用邏輯和樣式&#xff0c;但我很快會遇到一個問題&#xff1a;在控件內部如何支持數據綁定和屬性變更通知&#xff1f;特別是我們繼承自 Control 的時候&#xff0c;已經不能再繼承…

DOM型XSS破壞

目錄 首先 然后 第一種 第二種&#xff08;DOM&#xff09; HTMLCollection HTML Relationships Custom 解 首先 <script>//urlencode解碼 //location接口的hash屬性是一個字符串&#xff0c;包含一個“#”后跟位置URL的片段標識符。如果URL沒有片段標識符&#…

Linux C 多線程基本操作

我們已經了解進程的基本概念&#xff1a;進程是正在執行的程序&#xff0c;并且是系統資源分配的基本單位。當用戶需要在一臺計算機上去完成多個獨立的工作任務時&#xff0c;可以使用多進程的方式&#xff0c;為每個獨立的工作任務分配一個進程。多進程的管理則由操作系統負責…

C語言基礎:二維數組練習題

1. 一個二維數組賦了初值&#xff0c;用戶輸入一個數&#xff0c;在該二維數組中查找。找到則返回行列位置&#xff0c;沒找到則提示。#include <stdio.h>int main() {int arr[3][3] {{1, 2, 3},{4, 5, 6},{7, 8, 9}};int t;printf("要查找的數&#xff1a;")…

Java面試題034:一文深入了解MySQL(6)

Java面試題029&#xff1a;一文深入了解MySQL&#xff08;1&#xff09; Java面試題030&#xff1a;一文深入了解MySQL&#xff08;2&#xff09; Java面試題031&#xff1a;一文深入了解MySQL&#xff08;3&#xff09; Java面試題032&#xff1a;一文深入了解MySQL&#x…

Java基礎教程(011):面向對象中的構造方法

10-面向對象-構造方法 構造方法也叫做構造器、構造函數。 作用&#xff1a;在創建對象的時候給成員變量進行初始化的。 ? 一、構造方法的特點特點說明與類同名構造方法的名稱必須與類名相同沒有返回類型構造方法沒有返回值&#xff0c;甚至不能寫 void自動調用使用 new 創建對…

Adobe Photoshop:數字圖像處理的終極工具指南

Hi&#xff0c;我是布蘭妮甜 &#xff01;Adobe Photoshop自1990年問世以來&#xff0c;已經成為數字圖像處理領域的標桿和代名詞。這款強大的軟件不僅徹底改變了攝影、設計和藝術創作的方式&#xff0c;還深刻影響了我們消費和感知視覺內容的文化方式。從專業攝影師到社交媒體…

本期來講講什么是LVS集群?

集群和分布式 集群&#xff08;Cluster&#xff09;&#xff0c;解決某個問題將多臺計算機組合形成的系統群。 常見的集群類型&#xff1a; 負載均衡(LoadBalancing&#xff0c;簡稱LB)&#xff1a;由多個相同配置的主機組成&#xff0c;每個主機經過調度承擔部分訪問&#…

JVM 類加載過程筆記

一、概述 JVM&#xff08;Java Virtual Machine&#xff09;在運行 Java 程序時&#xff0c;需要將 .class 字節碼文件加載到內存中&#xff0c;并轉換成可以被 JVM 執行的數據結構&#xff0c;這一過程就是 類加載過程&#xff08;Class Loading Process&#xff09;。 JVM 的…

基于爬蟲技術的電影數據可視化系統 Python+Django+Vue.js

本文項目編號 25002 &#xff0c;文末自助獲取源碼 \color{red}{25002&#xff0c;文末自助獲取源碼} 25002&#xff0c;文末自助獲取源碼 目錄 一、系統介紹二、系統錄屏三、啟動教程四、功能截圖五、文案資料5.1 選題背景5.2 國內外研究現狀 六、核心代碼6.1 查詢數據6.2 新…

如何用 LUKS 和 cryptsetup 為 Linux 配置加密

在信息安全愈發重要的今天&#xff0c;為 Linux 系統盤配置全盤加密已經成為很多企業和個人的選擇。LUKS&#xff08;Linux Unified Key Setup&#xff09;配合工具 cryptsetup 可以在不犧牲性能的前提下實現高強度加密。本文將通過一個故事化的場景&#xff0c;介紹整個配置過…

VIVADO技巧_BUFGMUX時序優化

1.版本說明日期作者版本說明2025xxxx風釋雪初始版本 2.概述 基于VIVADO時序約束&#xff0c;BUFGMUX多路時鐘選擇原語的設計3.原語介紹 7系列FPGA/UltraSCale/UltraSCaleBUFGMUX_CTRL BUFGMUX_CTRL_inst (.O(O), // 1-bit output: Clock output.I0(I0), // 1-bit input: Cloc…

服務器系統時間不準確怎么辦?

服務器系統時間不準確可能會導致日志錯亂、任務調度失敗、SSL證書校驗錯誤等問題。以下是解決辦法&#xff1a;&#x1f310; 一、同步系統時間的方法1. 使用 timedatectl 命令&#xff08;適用于 systemd 系統&#xff09;timedatectl set-ntp true # 開啟自動同步 timedatect…

零信任產品聯合寧盾泛終端網絡準入,打造隨需而變、精準貼合業務的網絡安全訪問體系

零信任網絡訪問控制&#xff08;Zero Trust Network Access&#xff0c;ZTNA&#xff0c;文中零信任皆指 ZTNA&#xff09;基于“永不信任&#xff0c;持續驗證”的理念&#xff0c;打破了企業基于傳統網絡邊界進行防護的固有模式。在當前日趨復雜的網絡環境下&#xff0c;內部…

【未限制消息消費導致數據庫CPU告警問題排查及解決方案】

一、背景 某天下午&#xff0c;上游系統同一時間突然下了三個大合同數據&#xff0c;平均每個合同數據實例在6萬以上的量級&#xff0c;短短幾分鐘內瞬間有20萬左右的流量涌入系統。 而在正常情況下&#xff0c;系統1天處理的流量也不過2千量級&#xff0c;當時數據庫指標監控告…

iOS開發 Swift 速記2:三種集合類型 Array Set Dictionary

初級代碼游戲的專欄介紹與文章目錄-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代碼都將會位于ctfc庫中。已經放入庫中我會指出在庫中的位置。 這些代碼大部分以Linux為目標但部分代碼是純C的&#xff0c;可以在任何平臺上使用。 源碼指引&#xff1a;github源…

Apache基礎配置

一、Apache安裝# 安裝apache [rootwebserver ~]# yum install httpd -y# 在防火墻中放行web服務 [rootwebserver ~]# firewall-cmd --permanent --add-servicehttp success [rootwebserver ~]# firewall-cmd --permanent --add-servicehttps success# 開啟服務 [rootwebserver …

Python100個庫分享第37個—BeautifulSoup(爬蟲篇)

目錄專欄導讀&#x1f4da; 庫簡介&#x1f3af; 主要特點&#x1f6e0;? 安裝方法&#x1f680; 快速入門基本使用流程解析器選擇&#x1f50d; 核心功能詳解1. 基本查找方法find() 和 find_all()CSS選擇器2. 屬性操作3. 文本提取&#x1f577;? 實戰爬蟲案例案例1&#xff…