当前位置: 首页 > news >正文

从LiveData迁移到Kotlin的 Flow,才发现是真的香!

LiveData 对于 Java 开发者、初学者或是一些简单场景而言仍是可行的解决方案。而对于一些其他的场景,更好的选择是使用 Kotlin 数据流 (Kotlin Flow)。虽说数据流 (相较 LiveData) 有更陡峭的学习曲线,但由于它是 JetBrains 力挺的 Kotlin 语言的一部分,且 Jetpack Compose 正式版即将发布,故两者配合更能发挥出 Kotlin 数据流中响应式模型的潜力。

此前一段时间,我们探讨了 如何使用 Kotlin 数据流 来连接您的应用当中除了视图和 View Model 以外的其他部分。而现在我们有了 一种更安全的方式来从 Android 的界面中获得数据流,已经可以创作一份完整的迁移指南了。

在这篇文章中,您将学到如何把数据流暴露给视图、如何收集数据流,以及如何通过调优来适应不同的需求。

数据流: 把简单复杂化,又把复杂变简单

LiveData 就做了一件事并且做得不错: 它在 缓存最新的数据 和感知 Android 中的生命周期的同时将数据暴露了出来。稍后我们会了解到 LiveData 还可以 启动协程 和 创建复杂的数据转换,这可能会需要花点时间。

接下来我们一起比较 LiveData 和 Kotlin 数据流中相对应的写法吧:

#1: 使用可变数据存储器暴露一次性操作的结果

这是一个经典的操作模式,其中您会使用协程的结果来改变状态容器:

△ 将一次性操作的结果暴露给可变的数据容器 (LiveData)

<!--Copyright2020GoogleLLC.SPDX-License-Identifier:Apache-2.0-->classMyViewModel{privateval_myUiState=MutableLiveData<Result<UiState>>(Result.Loading)valmyUiState:LiveData<Result<UiState>>=_myUiState// 从挂起函数和可变状态中加载数据
init{viewModelScope.launch{valresult=..._myUiState.value=result}}}

如果要在 Kotlin 数据流中执行相同的操作,我们需要使用 (可变的) StateFlow (状态容器式可观察数据流):

△ 使用可变数据存储器 (StateFlow) 暴露一次性操作的结果

classMyViewModel{privateval_myUiState=MutableStateFlow<Result<UiState>>(Result.Loading)valmyUiState:StateFlow<Result<UiState>>=_myUiState// 从挂起函数和可变状态中加载数据
init{viewModelScope.launch{valresult=..._myUiState.value=result}}}

StateFlowSharedFlow 的一个比较特殊的变种,而 SharedFlow 又是 Kotlin 数据流当中比较特殊的一种类型。StateFlow 与 LiveData 是最接近的,因为:

它始终是有值的。

它的值是唯一的。

它允许被多个观察者共用 (因此是共享的数据流)。

它永远只会把最新的值重现给订阅者,这与活跃观察者的数量是无关的。

当暴露 UI 的状态给视图时,应该使用 StateFlow。这是一种安全和高效的观察者,专门用于容纳 UI 状态。

#2: 把一次性操作的结果暴露出来

这个例子与上面代码片段的效果一致,只是这里暴露协程调用的结果而无需使用可变属性。

如果使用 LiveData,我们需要使用 LiveData 协程构建器:

△ 把一次性操作的结果暴露出来 (LiveData)

classMyViewModel(...):ViewModel(){valresult:LiveData<Result<UiState>>=liveData{emit(Result.Loading)emit(repository.fetchItem())}}

由于状态容器总是有值的,那么我们就可以通过某种 Result 类来把 UI 状态封装起来,比如加载中、成功、错误等状态。

与之对应的数据流方式则需要您多做一点配置:

△ 把一次性操作的结果暴露出来 (StateFlow)

classMyViewModel(...):ViewModel(){valresult:StateFlow<Result<UiState>>=flow{emit(repository.fetchItem())}.stateIn(scope=viewModelScope,started=WhileSubscribed(5000),//由于是一次性操作,也可以使用 Lazily 
initialValue=Result.Loading)}

stateIn 是专门将数据流转换为 StateFlow 的运算符。由于需要通过更复杂的示例才能更好地解释它,所以这里暂且把这些参数放在一边。

#3: 带参数的一次性数据加载

比方说您想要加载一些依赖用户 ID 的数据,而信息来自一个提供数据流的 AuthManager:

△ 带参数的一次性数据加载 (LiveData)

使用 LiveData 时,您可以用类似这样的代码:

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:LiveData<String?>=authManager.observeUser().map{user->user.id}.asLiveData()valresult:LiveData<Result<Item>>=userId.switchMap{newUserId->liveData{emit(repository.fetchItem(newUserId))}}}

switchMap 是数据变换中的一种,它订阅了 userId 的变化,并且其代码体会在感知到 userId 变化时执行。

如非必须要将 userId 作为 LiveData 使用,那么更好的方案是将流式数据和 Flow 结合,并将最终的结果 (result) 转化为 LiveData。

class MyViewModel(authManager..., repository...) : ViewModel() {private val userId: Flow<UserId> = authManager.observeUser().map { user -> user.id }val result: LiveData<Result<Item>> = userId.mapLatest { newUserId ->repository.fetchItem(newUserId)}.asLiveData()
}

如果改用 Kotlin Flow 来编写,代码其实似曾相识:

△ 带参数的一次性数据加载 (StateFlow)

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:Flow<UserId>=authManager.observeUser().map{user->user.id}valresult:StateFlow<Result<Item>>=userId.mapLatest{newUserId->repository.fetchItem(newUserId)}.stateIn(scope=viewModelScope,started=WhileSubscribed(5000),initialValue=Result.Loading)}

假如说您想要更高的灵活性,可以考虑显式调用 transformLatest 和 emit 方法:

val result = userId.transformLatest { newUserId ->emit(Result.LoadingData)emit(repository.fetchItem(newUserId))}.stateIn(scope = viewModelScope, started = WhileSubscribed(5000), initialValue = Result.LoadingUser //注意此处不同的加载状态)

#4: 观察带参数的数据流

接下来我们让刚才的案例变得更具交互性。数据不再被读取,而是被观察,因此我们对数据源的改动会直接被传递到 UI 界面中。

继续刚才的例子: 我们不再对源数据调用 fetchItem 方法,而是通过假定的 observeItem 方法获取一个 Kotlin 数据流。

若使用 LiveData,可以将数据流转换为 LiveData 实例,然后通过 emitSource 传递数据的变化。

△ 观察带参数的数据流 (LiveData)

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:LiveData<String?>=authManager.observeUser().map{user->user.id}.asLiveData()valresult=userId.switchMap{newUserId->repository.observeItem(newUserId).asLiveData()}}

或者采用更推荐的方式,把两个流通过 flatMapLatest 结合起来,并且仅将最后的输出转换为 LiveData:

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:Flow<String?>=authManager.observeUser().map{user->user?.id}valresult:LiveData<Result<Item>>=userId.flatMapLatest{newUserId->repository.observeItem(newUserId)}.asLiveData()}

使用 Kotlin 数据流的实现方式非常相似,但是省下了 LiveData 的转换过程:

△ 观察带参数的数据流 (StateFlow)

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:Flow<String?>=authManager.observeUser().map{user->user?.id}valresult:StateFlow<Result<Item>>=userId.flatMapLatest{newUserId->repository.observeItem(newUserId)}.stateIn(scope=viewModelScope,started=WhileSubscribed(5000),initialValue=Result.LoadingUser)}

每当用户实例变化,或者是存储区 (repository) 中用户的数据发生变化时,上面代码中暴露出来的 StateFlow 都会收到相应的更新信息。

#5: 结合多种源: MediatorLiveData -> Flow.combine

MediatorLiveData 允许您观察一个或多个数据源的变化情况,并根据得到的新数据进行相应的操作。通常可以按照下面的方式更新 MediatorLiveData 的值:

val liveData1: LiveData<Int> = ...
val liveData2: LiveData<Int> = ...val result = MediatorLiveData<Int>()result.addSource(liveData1) { value ->result.setValue(liveData1.value ?: 0 + (liveData2.value ?: 0))
}
result.addSource(liveData2) { value ->result.setValue(liveData1.value ?: 0 + (liveData2.value ?: 0))
}

同样的功能使用 Kotlin 数据流来操作会更加直接:

valflow1:Flow<Int>=...valflow2:Flow<Int>=...valresult=combine(flow1,flow2){a,b->a+b}

此处也可以使用 combineTransform 或者 zip 函数。

通过 stateIn 配置对外暴露的 StateFlow

早前我们使用 stateIn 中间运算符来把普通的流转换成 StateFlow,但转换之后还需要一些配置工作。如果现在不想了解太多细节,只是想知道怎么用,那么可以使用下面的推荐配置:

valresult:StateFlow<Result<UiState>>=someFlow.stateIn(scope=viewModelScope,started=WhileSubscribed(5000),initialValue=Result.Loading)

不过,如果您想知道为什么会使用这个看似随机的 5 秒的 started 参数,请继续往下读。

根据文档,stateIn 有三个参数:‍

@paramscope共享开始时所在的协程作用域范围@paramstarted控制共享的开始和结束的策略@paraminitialValue状态流的初始值当使用[SharingStarted.WhileSubscribed]并带有`replayExpirationMillis`参数重置状态流时,也会用到initialValue。

started 接受以下的三个值:

Lazily: 当首个订阅者出现时开始,在 scope 指定的作用域被结束时终止。

Eagerly: 立即开始,而在 scope 指定的作用域被结束时终止。

WhileSubscribed: 这种情况有些复杂 (后文详聊)。

对于那些只执行一次的操作,您可以使用 Lazily 或者 Eagerly。然而,如果您需要观察其他的流,就应该使用 WhileSubscribed 来实现细微但又重要的优化工作,参见后文的解答。

WhileSubscribed 策略

WhileSubscribed 策略会在没有收集器的情况下取消上游数据流。通过 stateIn 运算符创建的 StateFlow 会把数据暴露给视图 (View),同时也会观察来自其他层级或者是上游应用的数据流。让这些流持续活跃可能会引起不必要的资源浪费,例如一直通过从数据库连接、硬件传感器中读取数据等等。当您的应用转而在后台运行时,您应当保持克制并中止这些协程

WhileSubscribed 接受两个参数:

publicfunWhileSubscribed(stopTimeoutMillis:Long=0,replayExpirationMillis:Long=Long.MAX_VALUE)

超时停止

根据其文档:

stopTimeoutMillis 控制一个以毫秒为单位的延迟值,指的是最后一个订阅者结束订阅与停止上游流的时间差。默认值是 0 (立即停止)。

这个值非常有用,因为您可能并不想因为视图有几秒钟不再监听就结束上游流。这种情况非常常见——比如当用户旋转设备时,原来的视图会先被销毁,然后数秒钟内重建。

liveData 协程构建器所使用的方法是 添加一个 5 秒钟的延迟,即如果等待 5 秒后仍然没有订阅者存在就终止协程。前文代码中的 WhileSubscribed (5000) 正是实现这样的功能:

class MyViewModel(...) : ViewModel() {val result = userId.mapLatest { newUserId ->repository.observeItem(newUserId)}.stateIn(scope = viewModelScope, started = WhileSubscribed(5000), initialValue = Result.Loading)
}

这种方法会在以下场景得到体现:

用户将您的应用转至后台运行,5 秒钟后所有来自其他层的数据更新会停止,这样可以节省电量。

最新的数据仍然会被缓存,所以当用户切换回应用时,视图立即就可以得到数据进行渲染。

订阅将被重启,新数据会填充进来,当数据可用时更新视图。

数据重现的过期时间

如果用户离开应用太久,此时您不想让用户看到陈旧的数据,并且希望显示数据正在加载中,那么就应该在 WhileSubscribed 策略中使用 replayExpirationMillis 参数。在这种情况下此参数非常适合,由于缓存的数据都恢复成了 stateIn 中定义的初始值,因此可以有效节省内存。虽然用户切回应用时可能没那么快显示有效数据,但至少不会把过期的信息显示出来。

replayExpirationMillis 配置了以毫秒为单位的延迟时间,定义了从停止共享协程到重置缓存 (恢复到 stateIn 运算符中定义的初始值 initialValue) 所需要等待的时间。它的默认值是长整型的最大值 Long.MAX_VALUE (表示永远不将其重置)。如果设置为 0,可以在符合条件时立即重置缓存的数据。

从视图中观察 StateFlow

我们此前已经谈到,ViewModel 中的 StateFlow 需要知道它们已经不再需要监听。然而,当所有的这些内容都与生命周期 (lifecycle) 结合起来,事情就没那么简单了。

要收集一个数据流,就需要用到协程。Activity 和 Fragment 提供了若干协程构建器:

Activity.lifecycleScope.launch : 立即启动协程,并且在本 Activity 销毁时结束协程。

Fragment.lifecycleScope.launch : 立即启动协程,并且在本 Fragment 销毁时结束协程。

Fragment.viewLifecycleOwner.lifecycleScope.launch : 立即启动协程,并且在本 Fragment 中的视图生命周期结束时取消协程。

LaunchWhenStarted 和 LaunchWhenResumed

对于一个状态 X,有专门的 launch 方法称为 launchWhenX。它会在 lifecycleOwner 进入 X 状态之前一直等待,又在离开 X 状态时挂起协程。对此,需要注意对应的协程只有在它们的生命周期所有者被销毁时才会被取消

△ 使用 launch/launchWhenX 来收集数据流是不安全的

当应用在后台运行时接收数据更新可能会引起应用崩溃,但这种情况可以通过将视图的数据流收集操作挂起来解决。然而,上游数据流会在应用后台运行期间保持活跃,因此可能浪费一定的资源。

这么说来,目前我们对 StateFlow 所进行的配置都是无用功;不过,现在有了一个新的 API。

lifecycle.repeatOnLifecycle 前来救场

这个新的协程构建器 (自 lifecycle-runtime-ktx 2.4.0-alpha01 后可用) 恰好能满足我们的需要: 在某个特定的状态满足时启动协程,并且在生命周期所有者退出该状态时停止协程。

△ 不同数据流收集方法的比较

比如在某个 Fragment 的代码中:

onCreateView(...){viewLifecycleOwner.lifecycleScope.launch{viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED){myViewModel.myUiState.collect{...}}}}

当这个 Fragment 处于 STARTED 状态时会开始收集流,并且在 RESUMED 状态时保持收集,最终在 Fragment 进入 STOPPED 状态时结束收集过程。如需获取更多信息,请参阅: 使用更为安全的方式收集 Android UI 数据流。

结合使用 repeatOnLifecycle API 和上面的 StateFlow 示例可以帮助您的应用妥善利用设备资源的同时,发挥最佳性能。

△ 该 StateFlow 通过 WhileSubscribed(5000) 暴露并通过 repeatOnLifecycle(STARTED) 收集

注意: ref="https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.android.google.cn%2Ftopic%2Flibraries%2Fdata-binding%2Fobservability%23stateflow">近期在 Data Binding 中加入的 StateFlow 支持 使用了 launchWhenCreated 来描述收集数据更新,并且它会在进入稳定版后转而使用 repeatOnLifecyle

对于数据绑定,您应该在各处都使用 Kotlin 数据流并简单地加上 asLiveData() 来把数据暴露给视图。数据绑定会在 lifecycle-runtime-ktx 2.4.0 进入稳定版后更新。

总结

通过 ViewModel 暴露数据,并在视图中获取的最佳方式是:

✔️ 使用带超时参数的 WhileSubscribed 策略暴露 StateFlow。[示例 1]

✔️ 使用 repeatOnLifecycle 来收集数据更新。[示例 2]

如果采用其他方式,上游数据流会被一直保持活跃,导致资源浪费:

❌ 通过 WhileSubscribed 暴露 StateFlow,然后在 lifecycleScope.launch/launchWhenX 中收集数据更新。

❌ 通过 Lazily/Eagerly 策略暴露 StateFlow,并在 repeatOnLifecycle 中收集数据更新。

当然,如果您并不需要使用到 Kotlin 数据流的强大功能,就用 LiveData 好了 :)
ManuelWojtekYigit、Alex Cook、FlorinaChris 致谢!

Android核心知识点笔记(点击最下方卡片获取)

Android开发核心知识点笔记

Android Framework核心知识点笔记

Android Flutter核心知识点笔记与实战详解

音视频开发笔记,入门到高级进阶

性能优化核心知识点笔记

Android开发高频面试题,25个知识点整合

Android开发核心架构知识点笔记

相关文章:

从LiveData迁移到Kotlin的 Flow,才发现是真的香!

LiveData 对于 Java 开发者、初学者或是一些简单场景而言仍是可行的解决方案。而对于一些其他的场景&#xff0c;更好的选择是使用 Kotlin 数据流 (Kotlin Flow)。虽说数据流 (相较 LiveData) 有更陡峭的学习曲线&#xff0c;但由于它是 JetBrains 力挺的 Kotlin 语言的一部分&…...

【BOOST C++】组件编程(2)-- 组件的设计原理

GitHub - ros2/demos at foxy 一、说明 为了研究ROS2的组件编程&#xff0c;首先要理解如何何为组件。组件本是微软的发明物体&#xff0c;但是在ubuntu上需要自己从底层实现&#xff0c;就说ROS2不用你写&#xff0c;但是就能看明白也是需要一点理论功底的。本篇按照COM内幕的…...

基于单细胞多组学数据无监督构建基因调控网络

在单细胞分辨率下识别基因调控网络&#xff08;GRNs&#xff0c;gene regulatory networks&#xff09;一直是一个巨大的挑战&#xff0c;而单细胞多组学数据的出现为构建GRNs提供了机会。 来自&#xff1a;Unsupervised construction of gene regulatory network based on si…...

蓝桥杯-最优清零方案(2022省赛)

蓝桥杯-最优清零方案1、问题描述2、解题思路3、代码实现1、问题描述 给定一个长度为 N 的数列 1,2,⋯,A1,A2,...,ANA_1,A_2,...,A_NA1​,A2​,...,AN​ 。现在小蓝想通过若干次操作将 这个数列中每个数字清零。 每次操作小蓝可以选择以下两种之一: 1. 选择一个大于 0 的整数, 将…...

Mac免费软件下载网站推荐(最全免费,替代MacWk)

一、Appstorrent 官方网站&#xff1a; https://appstorrent.ru/ 这是一个俄语网站&#xff0c;其他很多网站资源都来自这里。点击右上角切换到中文。不需要登录网站&#xff0c;直接从软件详情页下载即可。体验非常好。 二、Xclient 官方网站&#xff1a; https://xclie…...

GPU是什么

近期ChatGPT十分火爆&#xff0c;随之而来的是M国开始禁售高端GPU显卡。M国想通过禁售GPU显卡的方式阻挡中国在AI领域的发展。 GPU是什么&#xff1f;GPU&#xff08;英语&#xff1a;Graphics Processing Unit&#xff0c;缩写&#xff1a;GPU&#xff09;是显卡的“大脑”&am…...

20230305学习计划

目录 第二天学习开发框架 前言 一、巩固复习第一天20230304学习笔记 二、SpringMVC中的控制器是不是单例模式&#xff1f;如果是&#xff0c;如何保证线程安全&#xff1f; 1、控制器是单例模式&#xff0c;是线程不安全的。 2、Spring中保证线程安全的方法&#xff1a; …...

SocketCan 应用编程

SocketCan 应用编程 由于 Linux 系统将 CAN 设备作为网络设备进行管理&#xff0c;因此在 CAN 总线应用开发方面&#xff0c;Linux 提供了SocketCAN 应用编程接口&#xff0c;使得 CAN 总线通信近似于和以太网的通信&#xff0c;应用程序开发接口更加通用&#xff0c;也更加灵…...

从零学习python - 04函数方法与返回值

函数&#xff1a;Function-也称为方法&#xff0c;是组织好的、可重复使用的&#xff0c;用来实现指定功能的代码块。函数的定义与调用:创建函数目的是封装业务逻辑&#xff0c;实现代码复用# 创建函数关键字:def(definition)def fun1(x, y):print(x y)函数的参数:python函数中…...

MySQL实战之事务到底是隔离的还是不隔离的

1.前言 我们在MySQL实战之事务隔离&#xff1a;为什么你改了我还看不见讲过事务隔离级别的时候提到过&#xff0c;如果是可重复读隔离级别&#xff0c;事务T启动的时候会创建一个视图read-view,之后事务T执行期间&#xff0c;即使有其他事务修改了数据&#xff0c;事务T看到的…...

Elasticsearch:理解 Master,Elections,Quorum 及 脑裂

集群中的每个节点都可以分配多个角色&#xff1a;master、data、ingest、ml&#xff08;机器学习&#xff09;等。 我们在当前讨论中感兴趣的角色之一是 master 角色。 在 Elasticsearch 的配置中&#xff0c;我们可以配置一个节点为 master 节点。master 角色的分配表明该节点…...

【致敬女神】HTMLReport应用之Unittest+Python+Selenium+HTMLReport项目自动化测试实战

HTMLReport应用之UnittestPythonSeleniumHTMLReport项目自动化测试实战1 测试框架结构2 技术栈3 实现思路3.1 使用HtmlTestRunner3.2 使用HTMLReport4 TestRunner参数说明4.1 源码4.2 参数说明5 框架代码5.1 common/reportOut.py5.2 common/sendMain.py5.3 report5.3.1 xxx.htm…...

JAVA的16 个实用代码优化小技巧

一、类成员与方法的可见性最小化 举例&#xff1a;如果是一个private的方法&#xff0c;想删除就删除。 如果一个public的service方法&#xff0c;或者一个public的成员变量&#xff0c;删除一下&#xff0c;不得思考很多。 二、使用位移操作替代乘除法 计算机是使用二进制…...

并发编程的三大挑战之原子性及其解决方案

目录 一、原子性问题 1、带来原子性问题的原因 2、如何解决线程切换带来的原子问题 2.1、使用synchronized关键字来保证 2.2、使用CAS来保证原子性 2.3、使用lock锁来保证 一、原子性问题 1、带来原子性问题的原因 线程切换是带来原子的根本原因&#xff0c;java的并发程…...

QML动画(其他的动画)

PauseAnimation &#xff08;暂停动画&#xff09; 为动画提供暂停 Rectangle{id:rect1width: 100;height: 100;x:100;y:100color: "lightBlue"SequentialAnimation{running: trueColorAnimation {target: rect1&#xff1b;property: "color"&#xff1b;…...

Spark 配置项

Spark 配置项硬件资源类CPU内存堆外内User Memory/Spark 可用内存Execution/Storage Memory磁盘ShuffleSpark SQLJoin 策略调整自动分区合并自动倾斜处理配置项分为 3 类: 硬件资源类 : 与 CPU、内存、磁盘有关的配置项Shuffle 类 : Shuffle 计算过程的配置项Spark SQL : Spar…...

掌握Vue3模板语法,助你轻松实现高效Web开发

Vue3作为前端开发中的一种主流框架&#xff0c;为我们提供了多种灵活的方式来处理模板语法。除了基础的模板语法&#xff0c;Vue3还提供了一些高级的语法&#xff0c;可以让我们更好地处理组件、响应式数据和UI逻辑等。在这篇博客中&#xff0c;我们将介绍Vue3中的一些高级模板…...

Jmeter+Ant+Jenkins接口自动化测试平台搭建

平台简介一个完整的接口自动化测试平台需要支持接口的自动执行&#xff0c;自动生成测试报告&#xff0c;以及持续集成。Jmeter支持接口的测试&#xff0c;Ant支持自动构建&#xff0c;而Jenkins支持持续集成&#xff0c;所以三者组合在一起可以构成一个功能完善的接口自动化测…...

ncnn部署(CMakelists.txt)

1. NCNN 环境安装 参考博客: 基于ncnn的yolov5模型部署 1. 1 protobuf编译 打开VS2013/VS2019的X64命令行(注意不是cmd),我这里以V32013环境进行编译 > cd <protobuf-root-dir> > mkdir build-vs2013 > cd build-vs2013 > cmake -G"NMake Makefil…...

SQL分库分表

什么是分库分表&#xff1f; 分库分表是两种操作&#xff0c;一种是分库&#xff0c;一种是分表。 分库分表又分为垂直拆分和水平拆分两种。 &#xff08;1&#xff09;分库&#xff1a;将原来存放在单个数据库中的数据&#xff0c;拆分到多个数据库中存放。 &#xff08;2&…...

大数据分析案例-基于逻辑回归算法构建微博评论情感分类模型

🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 喜欢大数据分析项目的小伙伴,希望可以多多支持该系列的其他文章 大数据分析案例合集…...

0105深度优先搜索算法非递归2种实现对比-无向图-数据结构和算法(Java)

1 两种非递归实现 在前面我们解决无向图的单点通性和单点路径问题时&#xff0c;都用到了深度优先搜索算法。深度优先搜索算法可以用递归和非递归两种方式。这里讨论非递归实现。 无向图结构使用邻接表实现。 第一种非递归方法&#xff08;推荐&#xff09;&#xff0c;代码如…...

传统手工数据采集耗时耗力?Smartbi数据填报实现数据收集分析自动化

企业在日常经营管理过程中&#xff0c;往往需要收集很多内外部的信息&#xff0c;清洗整理后再进行存储、分析、呈现、决策支持等各种作业&#xff0c;如何高效收集结构化数据是企业管理者经常要面对的问题。传统手工的数据采集方式不仅耗费了大量人力时间成本&#xff0c;还容…...

《Spring源码深度分析》第5章 Bean的加载

目录标题前言一、Bean加载入口与源码分析1、Bean加载的入口2、Bean加载源码二、FactoryBean的使用三、缓存中获取单例bean(待补充)前言 经过前面的分析&#xff0c;我们终于结束了对XML 配置文件的解析&#xff0c;接下来将会面临更大的挑战&#xff0c;就是对 bean 加载的探索…...

华为OD机试真题Java实现【求最大数字】真题+解题思路+代码(20222023)

求最大数字 题目 给定一个由纯数字组成以字符串表示的数值,现要求字符串中的每个数字最多只能出现2次,超过的需要进行删除;删除某个重复的数字后,其它数字相对位置保持不变。 如34533,数字3重复超过2次,需要删除其中一个3,删除第一个3后获得最大数值4533 请返回经过删…...

Java——异常机制

前言 随着对java的不断深入学习&#xff0c;在对语法以及编程思想有了一定的了解之后&#xff0c;在编程的过程中有可能会因为用户的输入不正确或者逻辑错误而出现异常或者错误&#xff0c;因此如何去捕捉与避免不应该出现的异常或者错误就变得十分重要。本文就介绍了java的异…...

【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate)12.2实时异构同步Oracle数据部署方案(下)

系列文章目录 【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate)12.2实时异构同步Oracle数据部署方案(上) 【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate)12.2实时异构同步Oracle数据部署方案(中) 【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate…...

ESP32设备驱动-土壤湿度传感器驱动

土壤湿度传感器驱动 1、土壤湿度传感器介绍 土壤湿度传感器由两个探头组成,用于测量水的体积含量。 两个探头让电流通过土壤,然后得到电阻值来测量水分值。 当有更多的水时,土壤会传导更多的电,这意味着电阻会更小。 因此,水分含量会更高。 干燥的土壤导电性差,所以当…...

公网远程连接MongoDB数据库【内网穿透】

文章目录1. 安装数据库2. 内网穿透2.1 创建隧道映射2.2 测试随机公网地址远程连接3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问MongoDB是一个基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供…...

SQL注入——floor报错注入

目录 一&#xff0c;涉及到的函数 rand&#xff08;&#xff09; floor&#xff08;&#xff09; concat_ws() as别名&#xff0c;group by分组 count() 报错原理 一&#xff0c;涉及到的函数 rand()函数&#xff1a;随机返回0~1间的小数 floor()函数&#xff1a;小数向…...

沈阳市网站建设/网推怎么推广

教材学习内容总结 串流 Java将输入/输出抽象化为串流&#xff0c;数据有来源及目的地&#xff0c;衔接两者的是串流对象。 从应用程序角度来看&#xff0c;如果要将数据从来源取出&#xff0c;可以使用输入串流&#xff0c;如果要将数据写入目的地&#xff0c;可以使用输出串流…...

一款非常不错的seo网站优化公司源码/安卓手机优化神器

csdn查看自己所有资源被下载次数 隐藏接口API如下&#xff1a; https://download-console-api.csdn.net/v1/user/sources/getUploadListByUserName?status2&pageNum1&pageSize100...

wordpress互动/东莞百度快速排名

2019独角兽企业重金招聘Python工程师标准>>> 常见swagger注解一览与使用 最常用的5个注解 Api&#xff1a;修饰整个类&#xff0c;描述Controller的作用 ApiOperation&#xff1a;描述一个类的一个方法&#xff0c;或者说一个接口 ApiParam&#xff1a;单个参数描述…...

长沙银狐做网站b/怎样免费制作网页

1.1.1 摘要 图1 C# 泛型介绍 在接触泛型之前&#xff0c;我们编程一般都是使用具体类型&#xff08;char, int, string等&#xff09;或自定义类型来定义我们变量&#xff0c;如果我们有一个功能很强的接口&#xff0c;而且我们想把它提取或重构成一个通用的接口&#xff0c;使…...

wordpress 显示指定文章标题/网站免费客服系统

文章目录什么是进程进程控制块进程状态两状态进程模型进程终止和创建进程的创建进程终止五状态进程模型被挂起的进程交换的需要挂起的其他用途从诸如Windows98的单用户系统到诸如IBM z/OS的可以支持成千上万个用户的主机系统&#xff0c;他们的创建都围绕着进程的概念。所以&am…...

java开发网站教程/关键词优化上海

TCP/IP协议(Transmission Control Protocol/Internet Protocol)叫做传输控制/网际协议&#xff0c;又叫网络通信协议。实际上&#xff0c;它包含上百个功能的协议&#xff0c;如ICMP(互联网控制信息协议)、FTP(文件传输协议)、UDP(用户数据包协议)、ARP(地址解析协议)等。TCP负…...