详解Jetpack Compose中的状态管理与使用

news2024/11/18 5:26:56

前言

  引用一段官方描述,如下

由于 Compose 是声明式工具集,因此更新它的唯一方法是通过新参数调用同一可组合项。这些参数是界面状态的表现形式。每当状态更新时,都会发生重组。因此,TextField 不会像在基于 XML 的命令式视图中那样自动更新。可组合项必须明确获知新状态,才能相应地进行更新。

  google想表达的是compose不会像xml布局一样,可以简单的在代码里主动调用方法(比如setText(),setImageResource()等等)就能去刷新UI内容,而是需要通过状态管理通知UI的内容需要更新。 

  而Compose的状态管理更符合MVVM思想的,虽然Jetpack很早之前就已经推出了ViewModel、LiveData、MutableLiveData、DataBinding(这个最有毒,开创了XML写逻辑的先河)作为状态管理。但是因为XML与Activity的定位原因,都让现在的Android编程很难说是MVVM模式,只能说是接近。

  其中尴尬的原因是:

  1.XML既是View层实现编写,但是又无法更新控制View层。并且XML的样式就已经表明它不适合编写逻辑控制View。

  2.Activity既是View层控制器,又不实现View的代码编写。

    他们本应该合二为一,但是却分开了。导致MVC,MVP,MVVM思想都无法完全契合Android平台,使很多新人在Android平台学习使用这3种思想时,会经常陷入困惑。

状态管理涉及到类与方法

  • remember:保存数据,并且在UI更新时会提供保存的值。但是Activity页面退出后会丢失保存的值
  • rememberSaveable:保存数据,并且将值写入到bundle中,然后重新构建Activity的时候,从bundle读数据。这表示Activity退出后也不会丢失值。
  • mutableStateOf :一个可变并且被Compose时刻观察的状态存储,作用就是让Compose可以获知数据已经改变,UI上的内容需要重新绘制。
  • mutableStateListOf:mutableStateOf只能观察单个类型数据的变化,无法观察到集合数据的变化。所以有了mutableStateListOf,方法参数带vararg关键字,所以它也可以是多个List组成的数组
  • mutableStateMapOf:同上,只不过是以哈希的形式,方法参数带vararg关键字,所以它也可以是数组
  • derivedStateOf:定义的对象状态依赖其他的对象状态时,需要使用derivedStateOf,当依赖对象状态发生改变,自己也可以跟着改变。

看完上面的可以明白,remember是用于临时保存数据的,而MutableState是用于通知与传递数据变化的。

remember与mutableStateOf 的使用例子(一个快速了解的Demo)

实现一个按键点击自增数值并且显示的Demo,一般情况下mutableStateOf 与 remember都是配合使用的(但是他们不是绑定关系,都可以单独使用)。直接使用mutableStateOf 与 remember组合使用的区别是什么?请看博客后面的”为什么mutableStateOf不能直接写到方法内部的例子“ 但是,建议你先保留疑问按顺序看下去。

下面代码里展示了3种创建方式,但是这3种方式都是不同的语法糖,结果是一样的。

代码:

class DeploymentActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyButton()
        }
    }

    @Preview(name = "按键自增计数")
    @Composable
    fun MyButton() {
        Column() {
            /*
            使用by需要引用
            import androidx.compose.runtime.getValue
            import androidx.compose.runtime.setValue
            */
            var count1 by remember { mutableStateOf(0) }
            Button(onClick = { count1++ }) {
                Text(text = "按键A = $count1")
            }

            var count2 = remember { mutableStateOf(0) }
            Button(onClick = { count2.value++ }) {
                Text(text = "按键B = ${count2.value}")
            }

            var (count3, setValue) = remember { mutableStateOf(0) }
            Button(onClick = { setValue.invoke(count3+1) }) {
                Text(text = "按键C = $count3")
            }
        }
    }
}

效果动图:

mutableStateListOf的使用例子

mutableStateListOf 是用在集合数据的情况下它能在集合数据变动的情况下触发重组,因为如果使用mutableStateOf将会无法观察到集合数据的变动,从而不触发重组。

private var mImageList = mutableStateListOf<String>()
    @Composable
    private fun collectContentList() {
        LazyVerticalGrid(
            columns = GridCells.Adaptive(minSize = 256.dp),
            verticalArrangement = Arrangement.spacedBy(20.dp),
            horizontalArrangement = Arrangement.spacedBy(20.dp),
            contentPadding = PaddingValues(top = 20.dp, start = 20.dp, end = 20.dp)
        ) {
            items(mImageList.size) { index ->
                AsyncImage(model = mImageList[index],
                    contentDescription = null,
                    contentScale = ContentScale.Crop,
                    modifier = Modifier
                        .width(256.dp)
                        .height(128.dp)
                        .pointerInput(Unit) {
                            detectTapGestures(
                                onTap = {
                                    DrawFromActivity.jumpCarryFileImage(
                                        context = requireContext(),
                                        mImageList[index]
                                    )
                                },
                                onLongPress = {
                                    mCurrentDeleteImagePath = mImageList[index]
                                    mIsShowDeleteDialog.value = true
                                }
                            )
                        }
                        .clip(RoundedCornerShape(10.dp)))
            }
        }
    }

 

mutableStateMapOf的使用例子

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyMapList()
        }
    }


    @Composable
    fun MyMapList() {
        val dataMap = remember {
            mutableStateMapOf("key1" to "value1",
            "key2" to "value2",
            "key3" to "value3",
            "key4" to "value4")
        }
        LazyColumn {
            items(dataMap.size){ index->
                val itemKey = dataMap.keys.toMutableList()[index]
                val itemValue = dataMap[itemKey]
                itemValue?.let { Text(text = it) }
            }
        }
    }

derivedStateOf的使用例子

 derivedStateOf的使用场景是,某个数据需要依靠其他状态管理的计算或者派生的情况。

代码例子如下:

我们需要计数,并且计数的结果派生一新的需求判断是奇数还是偶数。

    @Preview
    @Composable
    fun MyText() {
        val count = remember { mutableStateOf(0) }
        //是否是奇数
        val isOddNumber = remember {
            derivedStateOf {
                count.value % 2 != 0
            }
        }

        Text(text = "计数 = ${count.value} 是否是奇数 = ${isOddNumber.value}",
            color = Color.White,
            modifier = Modifier.clickable {
            count.value++
        })
    }

结果:

remember的带参使用例子

  remember不带参的使用例子已经在上面说明过了,不在重复举例。现在说说remember的带参使用例子。

  remember的代码块,只会在第一次创建的时候执行一次,后续就不会在执行了。如果我们有需求希望在Compose方法重组的时候remember的代码块在执行一次怎么办? 那就需要使用remember带参的情况,只要改变key就会让remember在compose重组的时候重新执行一次代码块。

举一个反面参考,不带参的代码例子:

    @Composable
    fun MyText() {
        //这个count是用来触发整个方法重组的
        val count = remember { mutableStateOf(0) }
        //不添加key
        val randomNum = remember() {
            Log.e("zh", "remember被重新执行代码块了")
            (0..99).random()
        }

        Text(text = "按键A = ${count.value}", modifier = Modifier.clickable {
            count.value++
            Log.e("zh", "randomNum = ${randomNum}")
        })
    }

 点击Text后重组的结果,可以看到随机数没有变化,固定在51,并且在remember代码块里的log日志也没有打印。

带参的例子:

请注意,因为remember是在Compose内部的所以,想让带参remember重新执行代码块就需要让Compose发生一次重组,所以下面的count是用来触发重组的。

    var key = 0

    @Composable
    fun MyText() {
        //这个count是用来触发整个方法重组的
        val count = remember { mutableStateOf(0) }
        //添加key
        val randomNum = remember(key) {
            Log.e("zh", "remember被重新执行代码块了")
            (0..99).random()
        }

        Text(text = "按键A = ${count.value}", modifier = Modifier.clickable {
            key++
            count.value++
            Log.e("zh", "key = ${key}")
            Log.e("zh", "randomNum = ${randomNum}")
        })
    }

点击Text后重组的结果,可以因为key的改变,randomNum的remember也被触发重新执行了代码块。从而更新了随机数的值。

rememberSaveable的使用例子

保存数据,并且将值写入到bundle中,然后重新构建Activity的时候,从bundle读数据。这表示Activity退出后也不会丢失值。

 代码:

    @Composable
    fun MyText() {
        val count = rememberSaveable {
            mutableStateOf(0)
        }

        Text(text = "计数 = ${count.value}  ",
            color = Color.Black,
            modifier = Modifier.clickable {
                count.value++
            })
    }

理解MutableState重组UI组件范围

mutableState的重组UI组件范围是在它读取与写入的范围里的。为了验证这个说法,请看下面的代码例子:

多个@Composable方法组合下的重组UI范围例子1:

下面的代码中,在Column被点击后,增加了count的数值但是并不会引起任何的UI重组。因为三个Text都没有引用count。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val count = remember { mutableStateOf(1) }
            Column(modifier = Modifier.clickable { count.value++ }) {
                Log.e("zh", "Column触发重组")
                Text1()
                Text2()
                Text3()
            }
        }
    }

    @Composable
    fun Text1() {
        Log.e("zh", "Text1触发重组")
        Text(text = "测试")
    }

    @Composable
    fun Text2() {
        Log.e("zh", "Text2触发重组")
        Text(text = "测试")
    }

    @Composable
    fun Text3() {
        Log.e("zh", "Text3触发重组")
        Text(text = "测试")
    }

多个@Composable方法组合下的重组UI范围例子2:

在下面的代码中Text1引用了count数据,所以在点击Columu增加了count数值后,重组范围只在自定义的Text1方法里,在外部的Column也没有触发重组。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val count = remember { mutableStateOf(1) }
            Column(modifier = Modifier.clickable { count.value++ }) {
                Log.e("zh", "Column触发重组")
                Text1(count)
                Text2(count)
                Text3(count)
            }
        }
    }

    @Composable
    fun Text1(count: MutableState<Int>) {
        Log.e("zh", "Text1触发重组 count = ${count.value} count内存地址= ${count}")
        Text(text = "测试 ${count.value}")
    }

    @Composable
    fun Text2(count: MutableState<Int>) {
        Log.e("zh", "Text2触发重组")
        Text(text = "测试")
    }

    @Composable
    fun Text3(count: MutableState<Int>) {
        Log.e("zh", "Text3触发重组")
        Text(text = "测试")
    }

@Composable方法内部的重组范围例子:

 在Text被点击后方法内部的所有组件都被重组了。但是有特例并不是所有情况下整个方法内部都会触发重组,在调用了Button、Surface、CompositionLocalProvider情况下重组范围只会被限制在这些组件的内部(其实Button、Surface内部含CompositionLocalProvider,导致的重组只会限制在他们的范围内)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyButton()
        }
    }

    @Composable
    fun MyButton() {
        val count = remember { mutableStateOf(1) }
        Log.e("zh", "触发重组1")
        Column {
            Log.e("zh", "触发重组2")
           Column {
               Log.e("zh", "触发重组3")
               Text(text = "数值 = ${count.value}", modifier = Modifier
                   .width(100.dp)
                   .height(100.dp)
                   .clickable { count.value++ })
           }
        }
    }

@Composable方法内部CompositionLocalProvider的重组范围例子:

Button、Surface内部含CompositionLocalProvider,所以一起举例。在下面的代码中,点击任何一个组件增加Count数值后,Column下的任何log都不会触发了,因为重组范围被限定在CompositionLocalProvider。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyButton()
        }
    }


    @OptIn(ExperimentalMaterialApi::class)
    @Composable
    fun MyButton() {
        val count = remember { mutableStateOf(1) }
        Log.e("zh", "触发重组1")
        Column {
            Log.e("zh", "触发重组2")
            Column {
                Log.e("zh", "触发重组3")
                Button(onClick = { count.value++ }) {
                    Log.e("zh", "Button触发重组")
                    Text(text = "Button = ${count.value}")
                }
                CompositionLocalProvider(){
                    Log.e("zh", "CompositionLocalProvider触发重组")
                    Text(text = "CompositionLocalProvider = ${count.value}", modifier = Modifier
                        .width(100.dp)
                        .height(100.dp)
                        .clickable { count.value++ })
                }
                Surface(onClick = { count.value++ },modifier = Modifier
                    .width(100.dp)
                    .height(100.dp)) {
                    Log.e("zh", "Surface触发重组")
                    Text(text = "Surface = ${count.value}")
                }
            }
        }
    }

为什么mutableStateOf不能直接写到方法内部的例子 :

在上面的例子里所有创建mutableStateOf的外部都套了一个remember。 那么肯定有人会疑问,为什么要增加remember? 不直接在方法内部创建mutableStateOf呢? 其实这个问题的关键是理解组件的重组。因为组件方法的每一次重组都会导致 mutableStateOf 被重新创建一次。remember的文字意思是记住,所以remember的作用就是将mutableStateOf或者其他实体数据引用到保存到每个Compose的SlotTable中,不受其重组的影响。

 在下面的代码中,我们故意错误的在组件方法里直接创建mutableStateOf。看看在Text点击后让Count自增后,重组后会引起什么问题:

    @SuppressLint("UnrememberedMutableState") //在内部调用mutableStateOf会出现警告
    @Composable
    fun MyButton() {
        val count = mutableStateOf(1)
        Log.e("zh", "count地址 = ${count}")
        Column {
            //因为Button含有CompositionLocalProvider不会导致外部也触发重组,所以这里用Text替代
            Text(text = "按键A = ${count.value}", modifier = Modifier.clickable { count.value++ })
            Log.e("zh", "count = ${count.value}")
        }
    }

结果就是每次组件方法的重组也把MutableState重新创建了,导致数值不会自增,并且内存地址每次都是新的。

但是mutableStateOf可以写在外部,下面代码中mCount1是保存在Activity这个类的全局变量中,而count2是保存在Composable创建的Compose的SlotTable中,但是二者在使用上没有什么特别大的区别。

    val mCount1 = mutableStateOf(1)

    @Composable
    fun MyText() {
        val count2 = remember { mutableStateOf(1) }
        Column {
            Text(text = "mCount1 ${mCount1}")
            Text(text = "count2 ${count2}")
        }
    }

MutableState通知UI重组机制

这里用下面的图片可以简单了解一下...  .MutableState的机制相当复杂,想要深入了解特别烧脑。因为代码追踪并不好用,你得用到debug调试才能找到他们的观察者消息的发送与接收。个人认为只要了解SnapshotMutableStateImpl,简单的理解State与快照Snapshot的机制即可。

remember的原理

下面用贴源码方式,展示remember的流程,看看remember将数据缓存到哪里去了。

 源码一

 

/**
 * 记住高阶函数calculation执行后产生的值。重组将总是返回产生的值
 */
@Composable
inline fun <T> remember(calculation: @DisallowComposableCalls () -> T): T =
    currentComposer.cache(false, calculation)

源码二

/**
 * A Compose compiler plugin API. DO NOT call directly.
 * 缓存记录,一个组合的组合数据中的值。编译器插件使用它来生成更有效的调用,以便在确定这些操作是安全的时候进行记录。
 */
@ComposeCompilerApi
inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
    @Suppress("UNCHECKED_CAST")
    return rememberedValue().let {
        if (invalid || it === Composer.Empty) {
            val value = block()
            updateRememberedValue(value)
            value
        } else it
    } as T
}

源码三

   override fun updateRememberedValue(value: Any?) = updateValue(value)

源码四

    /**
     * 将SlotTable的值更新为[value]的当前值。
     *
     * @param value the value to schedule to be written to the slot table.
     */
    @PublishedApi
    @OptIn(InternalComposeApi::class)
    internal fun updateValue(value: Any?) {
        if (inserting) {
            //插入新的值
            writer.update(value)
            if (value is RememberObserver) {
                record { _, _, rememberManager -> rememberManager.remembering(value) }
                abandonSet.add(value)
            }
        } else {
            //更新已经存在的值
            val groupSlotIndex = reader.groupSlotIndex - 1
            if (value is RememberObserver) {
                abandonSet.add(value)
            }
            recordSlotTableOperation(forParent = true) { _, slots, rememberManager ->
                if (value is RememberObserver) {
                    rememberManager.remembering(value)
                }
                //这里的set方法可以将新值保存到SlotTable里,并且将旧的值返回
                when (val previous = slots.set(groupSlotIndex, value)) {
                    is RememberObserver ->
                        //观察者记录管理类,将以前的注册的RememberObserver观察者移除
                        rememberManager.forgetting(previous)
                    //重组范围实施类
                    is RecomposeScopeImpl -> {
                        val composition = previous.composition
                        if (composition != null) {
                            //释放之前的值
                            previous.release()
                            //设置当前composition失效范围
                            composition.pendingInvalidScopes = true
                        }
                    }
                }
            }
        }
    }

remember存在的意义是什么

      在文章上面的 “为什么mutableStateOf不能直接写到方法内部的例子” 中已经讲解了大部分。这边在重复啰嗦一下,意义就是给每个Compose保存一份需要缓存的数据,使其不受到Compose重组的影响。这种设计是因为移动平台的应用有切换前后台需求,从而有页面生命周期的概念。需要Compose缓存一份数据用于前后台切换后的数据恢复展示。

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

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

相关文章

头歌计算机组成原理实验—运算器设计(9)第9关:原码一位乘法器设计

第9关&#xff1a;原码一位乘法器设计 实验目的 学生掌握原码一位乘法运算的基本原理&#xff0c;熟练掌握 Logisim 寄存器电路的使用&#xff0c;能在 Logisim 平台中设计实现一个 8*8位的无符号数乘法器。 视频讲解 ####实验内容 在 alu.circ 文件中的原码一位乘法器子电…

分布式消息中间件RocketMQ概述

RocketMQ 概述 MQ概述 MQ简介 ​ MQ&#xff0c;Message Queue&#xff0c;是一种提供消息队列服务的中间件&#xff0c;也称为消息中间件&#xff0c;是一套提供了消息生产、存储、消费全过程API的软件系统。消息即数据。一般消息的体量不会很大。 MQ用途 在网络上上可以查…

React学习笔记五-props

此文章是本人在学习React的时候&#xff0c;写下的学习笔记&#xff0c;在此纪录和分享。此为第五篇&#xff0c;主要介绍react中的props。 目录 1.props的基本使用 2.props的批量传递 2.1展开运算符的复习 2.1.1数组中的展开运算符 2.1.2函数中的展开运算符 2.1.3构造字面…

部署图的画法

部署图画法 1.部署图 1.1含义 部署图是把软件制品装配到计算机节点以及配置软件环境的工作 软件部署包含环境部署和软件制品部署 1.2软件部署 软件部署通过部署图对软件进行建模 1.3部署图 部署图常见的有制品 节点 设备 运行环境和部署规范 1.4部署图关系 在UML&…

UnityVR--Managers--对象池2

目录 前言 基本结构 对象池代码 对象池管理器代码 使用 总结 前言 经过上一篇对象池1的了解&#xff0c;已经做到了使用Unity自带的ObjectPool进行内存优化。本篇自己构建一个对象池管理器&#xff08;Manager&#xff09;&#xff0c;实现对象池的创建、删除、加载资源…

机器视觉工程师很苦吗?年轻人不怕苦,就怕学不到东西,机器视觉销售>项目经理>视觉>电气>机械>老板

年轻人不怕苦&#xff0c;就怕学不到东西。 对于年轻人来说&#xff0c;需要规划&#xff0c;更需要发展。如果学不到东西&#xff0c;就会限制其发展&#xff0c;最重要的体现就是限制待遇上限。 一个非标自动化公司出差的频次&#xff08;各个公司略有差别&#xff0c;大多…

062:cesium设置泛光折线材质(material-6)

第062个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置泛光折线材质,请参考源代码,了解PolylineGlowMaterialProperty的应用。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共89行)相关API参考:专…

蓝桥:前端开发笔面必刷题——Day2 数组(三)

文章目录 &#x1f4cb;前言&#x1f3af;两数之和 II&#x1f4da;题目内容✅解答 &#x1f3af;移除元素&#x1f4da;题目内容✅解答 &#x1f3af;有序数组的平方&#x1f4da;题目内容✅解答 &#x1f3af;三数之和&#x1f4da;题目内容✅解答 &#x1f4dd;最后 &#x…

Cloud Studio 内核升级之持续优化

前言 Cloud Studio 是基于浏览器的集成式开发环境&#xff08;IDE&#xff09;&#xff0c;为开发者提供了一个永不间断的云端工作站。用户在使用 Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器就能使用。云端开发体验与本地几乎一样&#xff0c;上手门槛更低&#…

Xcode 14.3 和 iOS 16.4 为 SwiftUI 带来了哪些新功能?

0. 概览 今年年初&#xff0c;Apple 推出了最新的 Xcode 14.3 以及对应的 iOS 16.4 。 与此同时&#xff0c;它们对目前最新的 SwiftUI 4.0 也添加了一些新功能&#xff1a; sheet 弹窗后部视图&#xff08;Interact with a view Behind a sheet&#xff09;可交互&#xff…

头歌计算机组成原理实验—运算器设计(7) 第7关:6位有符号补码阵列乘法器

第7关&#xff1a;6位有符号补码阵列乘法器 实验目的 帮助学生掌握补码阵列乘法器的实现原理。 视频讲解 实验内容 在 Logisim 中打开 alu.circ 文件&#xff0c;在6位补码阵列乘法器中利用5位阵列乘法器以及求补器等部件实现补码阵列乘法器&#xff0c;实验框架如图所示&a…

Linux - Shell 权限 权限管理 权限修改 权限身份的认证 目录的权限 粘滞位

shell命令以及运行原理 我们来输入指令的本质就是 输入字符串。 而指令的本质&#xff0c;就是编译好的文件和脚本&#xff0c;而只要是文件&#xff0c;就会在系统的特定路径下存放。 我们使用所有的指令最终都要在 OS &#xff08;操作系统&#xff09;内部运行&#xff0c;…

〖Python网络爬虫实战㉖〗- Selenium库和ChromeDriver驱动的安装

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付…

【C++】内存泄漏 智能指针

目录 一、什么是内存泄漏二、如何检测内存泄漏1、内存占用变化排查法2、valgrind定位法3、mtrace定位法 三、智能指针分类及作用1、unique_ptr2、shared_ptr3、weak_ptr 一、什么是内存泄漏 在实际的 C 开发中&#xff0c;我们经常会遇到诸如程序运行中突然崩溃、程序运行所用…

关于 HTTPS 的加密流程

目录 HTTP 与 HTTPS 的区别加密方式HTTPS 基本工作过程1. 仅使用对称密钥2. 引入非对称密钥对 key 进行加密3. 引入证书, 破解中间人攻击 HTTP 与 HTTPS 的区别 其实 HTTPS 与 HTTP 一样都是应用层协议, HTTPS 只是在 HTTP 的基础上再加上了一个加密层. 为啥要对 HTTP 进行加密…

bugku---misc

一.telnet 下载后是一个压缩包 条件反射&#xff0c;先丢在wireshark中看一下&#xff0c; 直接搜flag&#xff0c;就 出来了 Data: flag{d316759c281bf925d600be698a4973d5} 二.简单取证1 之前只做过取证大赛的&#xff0c;但是这个没有啥思路&#xff0c;看了一下需要工具m…

Redis集群简介及槽位映射(哈希取余和一致性哈希算法)

redis cluster需求至少需要3个master才能组成一个集群&#xff0c;同时每个sentinel至少有一个slave节点&#xff0c;各个节点之间保持tcp通信。当master发生宕机&#xff0c;redis cluster自动将对应的slave节点提拔为master,来重新对外提供服务。 先来说一下槽&#xff0c;集…

从“被动发现”变为“主动感知”|智能井盖脚下安全守护者

井盖作为城市基础设施的重要组成部分&#xff0c;具有关键的作用。城市的管道网络错综复杂&#xff0c;包括雨水、污水、弱电和强电等。其中&#xff0c;雨水和污水管道的管径较大&#xff0c;可能会导致隐藏或其他安全事故的发生。而弱电井则是整个城市信息传输的重要环节&…

Java 基础进阶篇(十四):File 类常用方法

File 类的对象代表操作系统的文件&#xff08;文件、文件夹&#xff09;&#xff0c;File 类在 java.io.File 包下。 File 类提供了诸如&#xff1a;创建文件对象代表文件&#xff0c;获取文件信息&#xff08;大小、修改时间&#xff09;、删除文件、创建文件&#xff08;文件…

Java【文件和IO】File 类, 字节IO流的使用

文章目录 前言一、File 类1, 构造方法2, 成员方法 二、字节流输入输出1, 字节流输入 InputStream1.1, 每次输入一个字节1.2, 每次输入多个字节 2, 字节流输出 OutputStream2.1, 每次输出一个字节2.2, 每次输出多个字节 总结 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望…