Jetpack Compose 中在屏幕间共享数据的 5 种方案

news2024/11/30 5:02:24

1. 路由传参

Jetpack Compose 中路由传参的方式有很多种,具体可以参考 Jetpack Compose 中的导航路由

以下是最简单的路由传参测试代码:

import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument

@Composable
fun NavigationArgsSample() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "screen1"
    ) {
        composable("screen1") {
            Screen1(onNavigateToScreen2 = {
                navController.navigate("screen2/$it")
            })
        }
        composable(
            route = "screen2/{my_param}",
            arguments = listOf(
                navArgument("my_param") {
                    type = NavType.StringType
                }
            )
        ) {
            val param = it.arguments?.getString("my_param") ?: ""
            Screen2(param = param)
        }
    }
}

@Composable
private fun Screen1(onNavigateToScreen2: (String) -> Unit) {
    Button(onClick = {
        onNavigateToScreen2("Hello world!")
    }) {
        Text(text = "Click me")
    }
}

@Composable
private fun Screen2(param: String) {
    Text(text = param)
}

代码很简单,就是从Screen1路由页面传一个值给Screen2路由页面。

这种方式不能算是真正的共享数据,它只能算是 “把我的数据分享给别人” 这种概念,当参数值到达别的页面以后,就成为该页面的本地静态数据,如果你修改了该数据也不会反馈到原始路由页面中。但是只从分享数据的角度,这种方式足够简单。

另外这种方式有一个好处是可以跨越系统内存不足杀死进程的情况而存活

我们可以通过 Android Studio Logcat 面板的 “Terminate Application” 按钮来模拟系统内存不足杀死进程的情况,但是当我打开我的 Android Studio 之后,不禁发出灵魂拷问:Where did my “Terminate Application” button gone ? 😑

在这里插入图片描述

好家伙,这个按钮居然没有了,如果你使用的是 Android Studio Dolphin 或者 Android Studio Flamingo 一定也会遇到这个问题,这个按钮对于模拟系统终止应用进程的场景还是非常有用的(注意它的作用不同于顶部工具栏中的stop按钮)。该怎么把它找回来呢?

不用慌,经过一番搜索,终于找到了该怎么把它找回来:Settings --> Experimental --> Enable new Logcat tool window. 将这个选项的打勾取消即可。

然后我们运行应用,将路由跳转到Screen2然后回到后台,这时点击 Terminate Application 模拟系统杀进程,效果如下:

在这里插入图片描述

可以发现这种方式确实可以在系统回收应用进程后,再次打开应用时,仍然保持之前的页面状态。

这种方式缺点也很明显,假如我们路由导航路径上有 7 个屏幕,不可能将数据从第一个传到第七个,那样太麻烦了。

2.共享ViewModel

我们知道ViewModel可以跨越Activity的配置变更而存活,因此它是一个Activity中不同的Composable组件之间共享数据的绝佳方案:

在这里插入图片描述

实际上ViewModel的作用域被限定为一个离他最近的 ViewModelStoreOwnerLifecycle。它会一直保留在内存中,直到其 ViewModelStoreOwner 永久消失。

ViewModelStoreOwner 对象可能是一个 ActivityFragment 或者一个 Navigation 导航图(NavBackStackEntry),具体取决于使用的场景。

如果我们打算在一个导航图之内的不同路由之间通过ViewModel来共享数据,就可以将其作用域限定为该导航图。

下面开始测试,首先创建一个用于共享的SharedViewModel

import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

class SharedViewModel: ViewModel() {

    private val _sharedState = MutableStateFlow(0)
    val sharedState = _sharedState.asStateFlow()

    fun updateState() {
        _sharedState.value++
    }

    override fun onCleared() {
        super.onCleared()
        println("ViewModel cleared")
    }
}

测试代码:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navigation
import com.fly.mycompose.application.examples.sharedata.model.SharedViewModel

@Composable
fun SharedViewModelSample() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "onboarding"
    ) {
        navigation(
            startDestination = "personal_details",
            route = "onboarding"
        ) {
            composable("personal_details") { entry ->
                val viewModel = entry.sharedViewModel<SharedViewModel>(navController,)
                val state by viewModel.sharedState.collectAsStateWithLifecycle()

                PersonalDetailsScreen(
                    sharedState = state,
                    onNavigate = {
                        viewModel.updateState()
                        navController.navigate("terms_and_conditions")
                    }
                )
            }
            composable("terms_and_conditions") { entry ->
                val viewModel = entry.sharedViewModel<SharedViewModel>(navController)
                val state by viewModel.sharedState.collectAsStateWithLifecycle()

                TermsAndConditionsScreen(
                    sharedState = state,
                    onOnboardingFinished = {
                        navController.navigate(route = "other_screen") {
                            popUpTo("onboarding") {
                                inclusive = true // 注意这里将 onboarding 整个导航图都弹出了
                            }
                        }
                    }
                )
            }
        }
        composable("other_screen") {
            Text(text = "Hello world")
        }
    }
}

@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(
    navController: NavHostController,
): T {
    val navGraphRoute = destination.parent?.route ?: return viewModel()
    val parentEntry = remember(this) {
        navController.getBackStackEntry(navGraphRoute)
    }
    return viewModel(parentEntry)
}

@Composable
private fun PersonalDetailsScreen(
    sharedState: Int,
    onNavigate: () -> Unit
) {
    Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
        Text(text = "State: $sharedState")
        Button(onClick = onNavigate) {
            Text(text = "Click me")
        }
        Text(text = "onboarding: PersonalDetailsScreen")
    }

}

@Composable
private fun TermsAndConditionsScreen(
    sharedState: Int,
    onOnboardingFinished: () -> Unit
) {
    Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
        Text(text = "State: $sharedState")
        Button(onClick = onOnboardingFinished) {
            Text(text = "go to other_screen")
        }
        Text(text = "onboarding: TermsAndConditionsScreen")
    }
}

上面代码中导航图onboarding的两个子路由personal_detailsterms_and_conditions共享一个SharedViewModel中的计数器的值,当在路由页面personal_details点击跳转到terms_and_conditions时,会同时将计数器的值加1,然后在terms_and_conditions就可以看到共享的计数器的值,假如返回,可以看到上一个页面共享的计数器的值跟当前是一致的。当从terms_and_conditions跳转到other_screen时,会把整个onboarding导航图都关闭,此时共享的SharedViewModel会被清除。

运行效果:

在这里插入图片描述

这里关键是在导航图的子路由之间共享ViewModel的代码:

@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(
    navController: NavHostController,
): T {
    val navGraphRoute = destination.parent?.route ?: return viewModel()
    val parentEntry = remember(this) {
        navController.getBackStackEntry(navGraphRoute)
    }
    return viewModel(parentEntry)
}

在 Jetpack架构组件库:Lifecycle、LiveData、ViewModel 中提到可以将 ViewModel 的作用域限定为指定的 ViewModelStoreOwner,而 Navigation 导航图 也是 ViewModelStoreOwner, 因此这里可以通过viewModel(parentEntry)来限定 ViewModel 的作用域限定为父导航图,这样当该导航图被关闭时, ViewModel 就会被清理(执行onCleared())而不会一直占用全局内存,那样可能导致内存泄漏。另外,注意查找父导航图的 NavBackStackEntry 是通过 navController.getBackStackEntry("parentNavigationRoute") 的方式来查找的。

3.共享单例的StateFlow

第三种方案就是使用全局单例进行共享,为了能够方便Compose观察状态,在单例内部持有一个StateFlow对象作为共享数据源。

// GlobalCounter.kt
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class GlobalCounter @Inject constructor() {

    private val _count = MutableStateFlow(0)
    val count = _count.asStateFlow()

    fun inc() {
        _count.value++
    }
}

注意这里的 GlobalCounter 使用 Hilt 注入为全局单例模式。

// Screen1ViewModel.kt
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class Screen1ViewModel @Inject constructor(
    private val counter: GlobalCounter
): ViewModel() {

    val count = counter.count

    fun inc() {
        counter.inc()
    }
}
// Screen2ViewModel.kt
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class Screen2ViewModel @Inject constructor(
    private val counter: GlobalCounter
): ViewModel() {

    val count = counter.count
}

测试代码:

import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.fly.mycompose.application.examples.sharedata.model.Screen1ViewModel
import com.fly.mycompose.application.examples.sharedata.model.Screen2ViewModel

@Composable
fun SingletonDependencySample() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "screen1"
    ) {
        composable("screen1") {
            val viewModel = hiltViewModel<Screen1ViewModel>()
            val count by viewModel.count.collectAsStateWithLifecycle()

            Screen1(
                count = count,
                onNavigateToScreen2 = {
                    viewModel.inc()
                    navController.navigate("screen2")
                }
            )
        }
        composable(
            route = "screen2"
        ) {
            val viewModel = hiltViewModel<Screen2ViewModel>()
            val count by viewModel.count.collectAsStateWithLifecycle()

            Screen2(count)
        }
    }
}

@Composable
private fun Screen1(
    count: Int,
    onNavigateToScreen2: () -> Unit
) {
    Button(onClick = {
        onNavigateToScreen2()
    }) {
        Text(text = "Count on screen1: $count")
    }
}

@Composable
private fun Screen2(count: Int) {
    Text(text = "Count on screen2: $count")
}

运行效果:

在这里插入图片描述

虽然使用全局单例能够在不同屏幕的Composable之间方便的共享数据,但是共享单例非常危险,这是因为单例类就是一个普通类,与其他类(如ViewModel)相比,它没有任何超能力,因此不能跨越Activity重建或者进程重建而存活,换句话说,一旦App进程意外终止,那么重新启动进程后,单例类内部的一切状态都归零,因为它没有采取任何恢复手段。

我们可以按照方案 1 中的模拟系统杀进程的方法进程测试:

在这里插入图片描述

可以看到在系统内存不足杀死App进程时,进程重建后并没有恢复之前的页面状态,我们保存的单例中的状态丢失了,也就是说单例此时处于初始化状态。

另外,在这个例子中,由于我们使用了 StateFlow ,它可以跨越屏幕旋转而存活,这是属于StateFlow 的特性,而非单例的特性。(为什么StateFlow是这样设计的呢?可以参考这里)

在这里插入图片描述

还有一个使用 StateFlow 带来的有趣现象是,当你短暂退出应用时,界面中从 StateFlow 收集的状态不会丢失,这也是 StateFlow 的特性, StateFlow 属于热流,在GC回收它之前,它的数据始终存在于内存之中,除非我们手动终止进程或者进程被系统回收。(更多关于StateFlow的内容可以参考这里)

在这里插入图片描述

可以看到,使用持有 StateFlow 的单例,除了不能跨越进程而存活外,也可以给我们带来一定的好处,即短暂的退出应用界面状态保持以及屏幕旋转的界面状态保持。但这不意味着它是安全的。

4.CompositionLocal

CompositionLocal 是一种可以组件树的上下游之间隐式传参的一种数据共享方案,一般用于主题、上下文之类的共享。需要共享数据的Composable之间需要满足父子嵌套关系才行,不能是同级的。

下面是测试代码:

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import kotlinx.coroutines.launch

val LocalSnackBarHostState = compositionLocalOf { SnackbarHostState() }

@Composable
fun AppRoot() {
    val snackBarHostState by remember { mutableStateOf(SnackbarHostState()) }
    CompositionLocalProvider(LocalSnackBarHostState provides snackBarHostState) {
        Scaffold(
            snackbarHost = { SnackbarHost(hostState = LocalSnackBarHostState.current) }
        ) { padding ->
            Box(modifier = Modifier.padding(padding)) {
                MyScreen()
            }
        }
    }
}

@Composable
private fun MyScreen() {
    val snackBarHostState = LocalSnackBarHostState.current
    val scope = rememberCoroutineScope()
    Button(
        onClick = {
            scope.launch { snackBarHostState.showSnackbar("Hello world!") }
        }
    ) {
        Text(text = "Show SnackBar")
    }
}

在这里插入图片描述

那么通过 CompositionLocal 共享的这种方案,能够跨越屏幕旋转和系统内存回收这种情况而保持状态吗?

为了便于观察,换一个计数器的测试代码示例:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp

val LocalCounter = compositionLocalOf { 0 }

@Composable
fun CompositionLocalCounter() {
    var counter by remember { mutableStateOf(0) }
    CompositionLocalProvider(LocalCounter provides counter) {
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(15.dp)
        ) {
            MyScreen()
            Button(onClick = { counter++ }) { Text("Add counter") }
        }
    }
}

@Composable
private fun MyScreen() {
    val counter = LocalCounter.current
    Text(text = "counter: $counter")
}

首先是屏幕旋转:

在这里插入图片描述

然后是模拟系统内存回收:

在这里插入图片描述

很显然, CompositionLocal 在这两种情况下都不能保持状态。

更多关于 CompositionLocal 的使用和介绍请参考 Jetpack Compose 中的 CompositionLocal

5.持久化存储

Jetpack Compose 中的状态持久化存储方案有很多,除了 DataStore 和 Room,你还可以使用第三方的存储库或者直接使用文件。

通过这种方式在屏幕间共享数据的好处也很明显:可以跨越任意形式的进程终止而永久存在,超越一切生死。 但是缺点可能就是比较慢。

下面是使用 SharedPreferences 来实现本地存储用户User信息的一个简单示例:

// Session.kt
data class Session(val user: User, val token: String, val expiresAt: Long)
data class User(val firstName: String, val lastName: String, val email: String)
interface SessionCache {
    fun saveSession(session: Session)
    fun getActiveSession(): Session?
    fun clearSession()
}
// SessionCacheImpl.kt
import android.content.SharedPreferences
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import javax.inject.Inject

class SessionCacheImpl @Inject constructor(
    private val sharedPreferences: SharedPreferences
): SessionCache {

    private val moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()
    private val adapter = moshi.adapter(Session::class.java)

    override fun saveSession(session: Session) {
        sharedPreferences.edit()
            .putString("session", adapter.toJson(session))
            .apply()
    }

    override fun getActiveSession(): Session? {
        val json = sharedPreferences.getString("session", null) ?: return null
        return adapter.fromJson(json)
    }

    override fun clearSession() {
        sharedPreferences.edit().remove("session").apply()
    }
}
// PersistentViewModel1.kt
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class PersistentViewModel1 @Inject constructor(
    private val sessionCache: SessionCache
): ViewModel() {

    val session get() = sessionCache.getActiveSession()

    fun saveSession() {
        sessionCache.saveSession(
            session = Session(
                user = User(
                    firstName = "Philipp",
                    lastName = "Lackner",
                    email = "test@test.com"
                ),
                token = "sample-token",
                expiresAt = 12345678910
            )
        )
    }
}
// PersistentViewModel2.kt
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class PersistentViewModel2 @Inject constructor(
    private val sessionCache: SessionCache
): ViewModel() {
    val session get() = sessionCache.getActiveSession()
}

测试代码:

// PersistentStorageSample.kt
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.fly.mycompose.application.examples.sharedata.model.PersistentViewModel1
import com.fly.mycompose.application.examples.sharedata.model.PersistentViewModel2

@Composable
fun PersistentStorageSample() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "screen1"
    ) {
        composable("screen1") {
            val viewModel = hiltViewModel<PersistentViewModel1>()
            LaunchedEffect(Unit) {
                println("Session: ${viewModel.session}")
            }
            Screen1(
                onNavigateToScreen2 = {
                    viewModel.saveSession()
                    navController.navigate("screen2")
                }
            )
        }
        composable(
            route = "screen2"
        ) {
            val viewModel = hiltViewModel<PersistentViewModel2>()
            LaunchedEffect(Unit) {
                println("Session: ${viewModel.session}")
            }
            Screen2()
        }
    }
}

@Composable
private fun Screen1(
    onNavigateToScreen2: () -> Unit
) {
    Button(onClick = {
        onNavigateToScreen2()
    }) {
        Text(text = "Go to next screen")
    }
}

@Composable
private fun Screen2() {
    Text(text = "Hello world")
}

总结

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/625998.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java开发框架:Spring介绍

Spring 概述特点Spring 程序遇到的问题与解决 概述 Spring 是 Java EE 编程领域中的一个轻量级开源框架&#xff0c;由 Rod Johnson 在 2002 年最早提出并随后创建&#xff0c;目的是解决企业级编程开发中的复杂性&#xff0c;实现敏捷开发的应用型框架 。其中&#xff0c;轻量…

代理模式的运用

文章目录 一、代理模式的运用1.1 介绍1.2 结构1.3 静态代理1.3.1 火车站买票案例类图1.3.2 代码 1.4 JDK动态代理1.4.1 代码1.4.2 JDK动态代理的执行流程 1.5 CGLIB动态代理1.5.1 导包1.5.2 代码 1.6 三种代理的对比1.7 优缺点1.8 使用场景 一、代理模式的运用 1.1 介绍 由于…

从0开始搭建Hyperledger Fabric2.x环境(fabric2.5版本)

Hyperledger Fabric 2.5环境搭建 一.Linux环境准备 # root登录 yum -y install git curl docker docker-compose tree yum -y install autoconf autotools-dev automake m4 perl yum -y install libtool autoreconf -ivf # 安装jq相关包 cd /opt git clone --recursive https…

软考A计划-系统架构师-官方考试指定教程-(8/15)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例

文章目录 0.前言1. 基本概念1.1. Stream的结构1.2. 持久化1.3. Stream的消费者组 2.实现原理2.1. Stream的数据结构2.2. Stream的消息追加2.3. Stream的消费2.4. Stream的消费者组 3.Redis Stream底层原理3.1. 基数树&#xff08;Radix Tree&#xff09;3.2. listpacks小结 4.命…

数据清洗、数据处理入门!R语言我来了,数据不再零散!

一、引言 数据清洗和预处理是数据科学中必不可少的一部分&#xff0c;它们能够帮助我们准确地分析和预测未来趋势。如果你曾经尝试过进行分析或建模&#xff0c;你会发现数据往往不像我们所想象的那样干净、整洁。需要对数据进行仔细的检查、清理和处理&#xff0c;才能真正把…

JavaSE笔记(五)重制版

泛型程序设计 在前面我们学习了最重要的类和对象&#xff0c;了解了面向对象编程的思想&#xff0c;注意&#xff0c;非常重要&#xff0c;面向对象是必须要深入理解和掌握的内容&#xff0c;不能草草结束。在本章节&#xff0c;我们还会继续深入了解&#xff0c;从泛型开始&a…

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作

P344-365 接口 "接口" 的概念和 "类" 特别是 "抽象类" 近似, Delphi 之初并没有接口, 后来(Delphi 3)为了支持 COM 引入了接口, 再后来发展成为 Delphi 重要的语言特性. 使用 COM 步骤可能是这样的:     1.程序在使用组件之初, 先联系 "…

Java-API简析_java.util.ArrayList类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131119289 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

2023最新ChatGPT商业版4.8.8支持GPT4.0+AI绘画midjourney+实时语音识别输入+后台一键版本更新!

2023最新ChatGPT商业版4.8.8支持GPT4.0AI绘画midjourney永久更新&#xff01; 1.网站系统源码介绍&#xff1a; 程序已支持ChatGPT4.0、Midjourney绘画、GPT3.5 API绘画、语音识别输入、用户会员套餐用户每日签到功能后台管理一键更新版本。支持手机电脑不同布局页面自适应。…

【MySQL 数据库】6、一篇文章学习【索引知识】,提高大数据量的查询效率【文末送书】

目录 一、索引概述二、索引结构(1) 不同类型的索引结构(2) 二叉树和红黑树(3) B 树(4) B树(5) Hash(6) 为什么InnoDB存储引擎选择使用Btree索引结构相对于二叉树&#xff0c;层级更少&#xff0c;搜索效率高&#xff1b; 三、索引的分类(1) 聚集索引和二级索引(2) 思考题 四、索…

STM32之FreeRTOS

目录 FreeRTOS 介绍 什么是 FreeRTOS &#xff1f; 为什么选择 FreeRTOS ? FreeRTOS 资料与源码下载 祼机开发与 FreeRTOS 祼机开发&#xff1a; FreeRTOS&#xff1a; FreeRTOS 实现多任务的原理 二、移植 FreeRTOS 手动移植 使用CubeMX快速移植 快速移植流程…

基于Hadoop与Electron的京东商品评论词云统计系统

一、综合设计目的与要求 合肥工业大学软件工程专业《云计算、大数据技术与应用》课程综合设计报告。 爬取京东或淘宝某一商品的评论1000条&#xff0c;统计词频&#xff08;使用MapReduce或HBase或Hive&#xff09;&#xff0c;并以词云的方式可视化呈现&#xff0c;最后设计为…

python-面向对象:三大特性高级特性

文章目录 前言一、面向对象三大特性&#xff1a;封装、继承、多态1.对象和类2.封装3.继承(1)重写父类方法(2)多继承(3)私有属性与私有方法 4.多态 二、三大特性的应用1.链表的封装2.栈的封装3.队列的封装4.二叉树的封装 三、高级特性1.类属性与实例属性2.类方法与静态方法3.pro…

三相PWM整流器滞环电流控制MATLAB仿真模型

三相PWM整流器滞环电流控制MATLAB仿真模型资源-CSDN文库https://download.csdn.net/download/weixin_56691527/87882090 模型简介&#xff1a; 该模型采用滞环电流控制方法来控制PWM整流器&#xff0c;在matlab/simulink中实现。电流内环采用三个滞环比较器&#xff0c;电压外…

C#,码海拾贝(38)——求解“线性方程组”的“高斯-赛德尔迭代法”之C#源代码

using System; namespace Zhou.CSharp.Algorithm { /// <summary> /// 求解线性方程组的类 LEquations /// 原作 周长发 /// 改编 深度混淆 /// </summary> public static partial class LEquations { /// <summary> /…

Nacos架构与原理 - 自研 Distro 协议 (AP分布式协议)

文章目录 背景设计思想Distro 协议工作原理数据初始化数据校验写操作读操作 小结 背景 Distro 协议是 Nacos 社区自研的⼀种 AP 分布式协议&#xff0c;是面向临时实例设计的⼀种分布式协议&#xff0c;其保证了在某些 Nacos 节点宕机后&#xff0c;整个临时实例处理系统依旧可…

VMware Workstation 17 安装教程

哈喽&#xff0c;大家好。今天一起学习的是VMware Workstation 17的安装&#xff0c;vm虚拟机是小编非常喜欢的生产力软件&#xff0c;小编之前发布的测试教程钧在vm上进行的实验。 VMware Workstation是一款功能强大的桌面虚拟计算机软件&#xff0c;它能够让用户在宿主机操作…

mac 电脑CPU温度怎么看?怎么可以监控Mac CPU温度,为什么我的 MacBook Air 这么热?

众所周知&#xff0c;电脑温度太高会直接影响到系统运行速度&#xff0c;对硬盘伤害也是很大的。特别是夏天&#xff0c;Mac 笔记本有时候运行起来会比较烫。关于 Mac 笔记本的散热&#xff0c;见仁见智。但是我们也比较好奇 CPU、电池的温度。怎么查看Mac CPU温度呢&#xff1…

【Linux工具】编译器、调式器、项目自动化构建工具以及git的使用(1编译器)

【Linux工具】编译器、调式器、项目自动化构建工具以及git的使用&#xff08;1&#xff09; 目录 【Linux工具】编译器、调式器、项目自动化构建工具以及git的使用&#xff08;1&#xff09;Linux编译器-gcc/g使用gcc的编译过程预处理&#xff08;进行宏替换&#xff09;编译&a…